Русский
Русский
English
Статистика
Реклама

Верификация

Перевод Будущее математики?

18.07.2020 22:06:54 | Автор: admin
В этом переводе презентации британского математика Кевина Баззарда мы увидим, что следующий комикс xkcd безнадежно устарел.

image

Каково будущее математики?


  • В 1990-х компьютеры стали играть в шахматы лучше людей.
  • В 2018 компьютеры стали играть в го лучше людей.
  • В 2019 исследователь искусственного интеллекта Christian Szegedy сказал мне, что через 10 лет компьютеры будут доказывать теоремы лучше, чем люди.

Конечно, он может быть не прав. А может быть и прав.

Я верю в следующее: через десять лет компьютеры будут помогать нам доказывать муторные теоремы уровня ранних аспирантов. В каких областях математики? Зависит от того, кого получится привлечь в эту область исследования.

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

Что такое доказательство?


Если спросить студента-отличника, математика-исследователя и компьютер о том, что такое доказательство, каков будет их ответ? Ответы студента-отличника и компьютера совпадут и будут следующим:
Доказательство это логическая последовательность утверждений, состоящая из аксиом выбранной системы, правил вывода и теорем, которые были доказаны ранее, которые, в конечном итоге, приводят к доказываевому утверждению.
Конечно, ответ математика-исследователя не будет таким идеалистичным. Для математика определением доказательства будет то, что другие опытные математики считают доказательством. Или то, что принято к публикации в журналы the Annals of Mathematics или Inventiones.

Однако с этим есть небольшая проблема. Следующие скриншоты взяты с сайта журнала the Annals of Mathematics, одного из самых престижных математических журналов в мире.

image

image

Вторая статья буквально противоречит первой. Авторы открыто пишут об этом в аннотации. Насколько я знаю, the Annals of Mathematics никогда не публиковала опровержений ни одной из этих работ. Какую из работ считают правильной опытные математики? Об этом можно узнать, только если вы работаете в этой области.

Вывод: в современной математике мнение по поводу того, является ли что-то доказательством, меняется с течением времени (например, может пройти путь от является до не является).

image

Эта короткая статья 2019 года указывает на то, что другая важная статья 2015 года, опубликованная в Inventiones, в значительной степени опирается на ложную лемму. Этого никто не замечал до 2019 года несмотря на то, что в 2016 году люди устраивали семинары по изучению этой важной работы.

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

Журнал Inventiones так и не опубликовал опровержение работы 2015 года.

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

Может быть моя работа в области p-адической программы Ленглендса опирается на неверные результаты. Или, более оптимистично, на верные результаты, но у которых нет полного доказательства.

Если наши исследования невоспроизводимы, можем ли мы это называть наукой? Я уверен на 99.9%, что p-адическая программа Ленглендса никогда не будет использована человечеством для того, чтобы сделать что-нибудь полезное. Если моя работа в математике не является полезной и не гарантирована быть верной на 100 процентов, это просто трата времени. Поэтому я решил прекратить исследования и сконцентрироваться на проверке известной математики на компьютере.

В 2019 году Balakrishnan, Dogra, Mueller, Tuitman и Vonk нашли все рациональные решения определенного уравнения четвертой степени в двух переменных. В явном виде:

$y^4 + 5x^4 - 6x^2y^2 + 6x^3 + 26x^2y + 10xy^2 10y^3 32x^2 2 -40xy + 24y^2 + 32x 16y = 0.$


Это вычисление имеет важное приложение в арифметике. Доказательство существенным образом опирается на вычисление в magma (система с закрытым исходным кодом), использующая быстрые нереферируемые алгоритмы. С большим трудом можно было бы перенести все вычисления на систему с открытым исходным кодом, например, sage. Однако никто не планирует этого делать.

Таким образом, часть доказательства остаётся скрытой. И возможно навсегда останется скрытой. Является ли этой наукой?

Пробелы в математике


  • В 1993 году Эндрю Уайлс объявил о доказательстве Великой теоремы Ферма. В доказательстве был пробел.
  • В 1994 году Уайлс и Ричард Тейлор устранили пробел, работа была опубликована и принята в математическом сообществе.
  • В 1995 году я указал Тейлору, что их доказательство использует работу Гросса, которая была не завершена. В работе Гросса было предположение, что линейные операторы Гекке, определенные на канонически изоморфных группах когомологий, коммутируют с каноническим изоморфизмом. Тейлор ответил мне, что с этим нет проблем, потому что он знает другой аргумент, который не опирается на работу Гросса.

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

А что если методы сильны, а авторы нет? Так возникают ситуации, когда наши доказательства не полны. Так возникают споры о том, доказаны ли наши теоремы на самом деле. Это совсем не то, как мы рассказываем о математике нашим студентам.

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

Большие пробелы в математики


Экспонат A


Классификация простых конечных групп. Эксперты утверждают, что у нас есть законченная классификация простых конечных групп. Я верю экспертам.

  • В 1983 году было объявлено о доказательстве классификации экспертами.
  • В 1994 году эксперты нашли ошибку (но давайте не будем раздувать из мухи слона?)
  • В 2004 году была опубликована 1000+ страничная работа. Эксперт в области Aschbacher утверждает, что ошибка была исправлена и объявляет план о публикации 12 томов полного доказательства.
  • В 2005 году только 6 из 12 томов было опубликовано
  • В 2010 году только 6 из 12 томов было опубликовано
  • В 2017 году только 6 из 12 томов было опубликовано
  • В 2018 году были опубликованы 7-й и 8-й тома и заметка о том, что публикация будет доделана к 2023 году.

Из трёх людей, руководящих проектом, один умер. Двум другим уже за семьдесят.

Экспонат B


Потенциальная модулярность абелевых поверхностей. Год назад мой выдающийся аспирант Toby Gee с тремя соавторами опубликовали 285-страничный препринт с результатом о том, что абелевы поверхности над тотально действительными полями являются потенциально модулярными.

Их доказательство цитирует три неопубликованных препринта (один 2018 года, один 2015 года, один 1990-х годов), записки 2007 года из интернета, неопубликованную диссертацию на немецком и работу, чьё главное утверждение было позднее опровергнуто. Более того, на 13 странице мы видим следующий текст:
Мы хотим обратить внимание на то, что мы используем Arthurs multiplicity formula для дискретного спектра GSp4, которая была объявлена в [Art04]. Доказательство, опирающееся на другие работы автора о симплектической и ортогональной группах, было дано в [GT18], но их доказательство имеет те же предположения, что и результаты в работе [Art13] и [MW16a, MW16b]. В частности, оно зависит от случаев twisted weighted fundamental lemma, которая была объявлена в [CL10], но доказательство которых ещё не было найдено. Более того, мы опираемся на ссылки [A24], [A25], [A26] и [A27] из работы [Art13], который на момент написания статьи ещё не опубликованы.
Может ли мы честно утверждать, что это наука?

Ссылка [CL10] выглядит следующим образом:
image
Работа, которая нужна моему аспиранту и соавторам так и не опубликована. Скорее всего, утверждение верно. Возможно даже доказуемо.

А это упомянутые ссылки из работы [Art13]:
image

В прошлом году я спросил Arthur про эти ссылки, и он ответил мне, что ни одна из работ еще не готова. Конечно, Jim Arthur гений. Он выиграл множество престижных наград. Но ему также 75 лет.

Экспонат C


GaitsgoryRozenblyum. В последнее время бесконечные категории обрели популярность. Со временем они станут ещё более важными. Работа Филдсовского лауреата Петера Шольце опирается на бесконечные категории.

Джейкоб Лурье написал 1000+ страничную работу об $(\infty, 1)$-категориях и включил много деталей в свою работу. GaitsgoryRozenblyum хотели получить аналогичных результаты про $(\infty, 2)$-категории, но в целях экономии времени опустили многие аргументы. Опущенные доказательства появятся где-нибудь ещё.

Я спросил Gaitsgory, как много было пропущено. Он ответил, что около 100 страниц. Я спросил Лурье, что он думает на этот счёт. Он ответил, что математи сильно различаются по тому, насколько комфортно для них опускать детали.

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

На конференции в университете Карнеги-Меллона, где я был на прошлой неделе, Markus Rabe рассказал мне, что Google работает над программой, которая будет переводить математические препринты с arxiv.org на язык, возможный для компьютерной проверки. А ещё я недавно видел работу, которая опирается на статью моего ученика, но ничего не упоминает про опущенные 100 страниц в [Art13].

Ошибка напоследок


image
Это очень интересный пример. Оригинальная работа была опубликована в журнале J. Funct. Anal. в 2013 году. В работе присутствует большая ошибка (неравенство в другую сторону). Ошибка была обнаружена S. Gouezel в 2017 году, когда Gouezel формализовал аргумент, используя компьютерную программу для проверки доказательств (Isabelle).

Новый аргумент представлен Gouezel и автором оригинальной работы. Новая работа не нуждается в рецензировании. Компьютер проверил 100 процентов нового аргумента. Методы оказались достаточно сильными, чтобы доказать теорему. И под доказательством я имею в виду классическое, чистое, определение доказательства то, которому мы учим наших студентов. Каждая деталь доказательства доступна читателю. Наука воспроизводима. Это математика, которую мы преподаем нашим студентам. Это и есть математика.

Вот другие примеры того, что, по-моему, является математикой:
  • Типичное доказательство уровня студента или магистра
  • Типичное доказательство столетней давности важного результата, который был хорошо задокументирован и исследован десятками тысяч математиков
  • Формальные доказательства авторства следующих математиков: Gonthier, Asperti, Avigad, Bertot, Cohen, Garillot, Le Roux, Mahboubi, OConnor, Ould Biha, Pasca, Rideau, Solovyev, Tassi и доказательство Thry теоремы FeitThompson
  • Формальное доказательство авторства следующих математиков: Hales, Adams, Bauer, Dat Tat Dang, Harrison, Truong Le Hoang, Kaliszyk, Magron, McLaughlin, Thang Tat Nguyen, Truong Quang Nguyen, Nipkow, Obua, Pleso, Rute, Solovyev, An Hoai Thi Ta, Trung Nam Tran, Diep Thi Trieu, Urban, Ky Khac Vu и доказательство Zumkeller гипотезы Кеплера.



На этом текст презентации заканчивается, потому что Кевин переходит к своей главной части: формальная верификация математических доказательств в системе Lean, разработанной Leo de Moura в Microsoft Research. К сожалению, примеры не вошли в слайды.

image

Автор огромный энтузиаст формальной верификации математических доказательств и ведёт очень интересный блог Xena на эту тему, который я очень рекомендую.
Подробнее..

Как мы делали универсальный сервис подписания для инфраструктуры Госуслуг на C и GO. Часть 1

18.03.2021 12:07:58 | Автор: admin

Это первая часть статьи, в которой будет рассмотрена GO-часть нового сервиса подписания для всем известного (а если не всем, то многим) портала Госуслуг. Сюда входит конфигурирование, тестирование, ресты, файловый менеджер и планировщик. В общем все то, что происходит до того, как данные будут переданы в C-часть для подписания и верификации. Я также кратко опишу основные предпосылки к созданию нового сервиса.

Дисклеймер!

Автор этой статьи не писатель, а разработчик, который пытается в первый раз интересно описать достаточно сложный проект.

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

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

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

Немного о терминах

Перед тем как начать, думаю, надо дать краткое описание некоторых терминов:

Единый портал государственных и муниципальных услуг (функций) (ЕПГУ, далее по тексту просто Госуслуги). Это портал, где у каждого гражданина есть возможность:

  • узнать информацию о государственных и муниципальных услугах;

  • заказать госуслуги в электронной форме;

  • записаться на приём в ведомство;

  • оплатить любым электронным способом штрафы Госавтоинспекции, судебные и налоговые задолженности, госпошлины, услуги ЖКХ;

  • оценить качество предоставления госуслуг.

Единая система межведомственного электронного взаимодействия (СМЭВ) информационная система, которая позволяет федеральным, региональным и местным органам власти, кредитным организациям (банкам), внебюджетным фондам и прочим участникам СМЭВ обмениваться данными, необходимыми для оказания государственных услуг гражданам и организациям в электронном виде, а также для реализации других своих функций

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

Мотивы и предпосылки

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

  1. Создания и верификации простых подписей

  2. Подписания и верификации запросов для СМЭВ2 по стандарту WS-Security + XMLDSig

  3. Подписания и верификации запросов для СМЭВ3 по стандарту XMLDSig

  4. Подписания и верификации файлов по стандартам CMS и CAdES-BES

  5. Подписания и верификации OAuth токенов для единой системы идентификации и аутентификации по стандартам CMS и CAdES-BES

  6. Подписания и верификации JWT

При этом поддерживаются ГОСТ-2012 как для 256, так и 512-битных ключей.

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

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

Финальный объем проекта сравнительно небольшой, приблизительно 28000 строк кода. Это особенно удивительно с учетом того, что у нас всего 4 внешних зависимости (libxml2 и КриптоПро, github.com/google/uuid, ithub.com/spf13/viper), что обязало нас взять на себя очень много работы с криптографическими стандартами.

GO-часть

Теперь предлагаю перейти к самому сервису подписания на C и GO. Чтобы повествование шло более логично, я начну описывать все с самого верха, начиная с конфигурационного файла. А затем буду спускаться все ниже и ниже, чтобы ближе к концу описание самых низкоуровневых операций и оптимизаций, которые как раз и позволяют сервису работать очень быстро, не осталось без контекста.

Конфигурационный файл

Конфигурационный файл у сервиса один, вследствие чего он получился достаточно большим, более 200 строчек.

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

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

Именно благодаря этому итоговый объем проекта 28000 строк, а не 60000 - 100000, что вполне естественно для проектов без четкой постановки и изменением планов в ходе разработки. Это также позволило создать легко расширяемую, универсальную архитектуру, благодаря которой добавление новых возможностей сейчас может занимать 1 2 дня, а не несколько недель.

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

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

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

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

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

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

Тестирование

В первую очередь для сервиса были написаны стандартные GO-тесты. В них нет ничего особенного, поэтому останавливаться на этом мы не будем.

Но одними стандартными тестами все не обошлось. По ходу развития сервиса появилась необходимость еще и в тестах, которые позволили бы проверять стабильности сервиса при больших нагрузках. А поскольку стандартные средства GO не позволяли сделать все необходимое, для этих целей был выбран JMeter. Впоследствии JMeter так же оказался очень удобным для профилирования C-части в связке с pprof.

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

Также в проекте используются линтеры из golangci-lint, которые позволяют лучше следить за качеством кода. Впрочем, на них можно подробно не останавливаться, на Хабре про линтеры много отдельных статей.

Ресты

Теперь наконец то можно перейти к тому, что эти самые тесты и тестируют, к рестам, которых на текущий момент в сервисе 17.

Один из них health-check, который возвращает основную информацию о текущем экземпляре сервиса. И еще 8 групп по 2 реста, которые отвечают за подпись и верификацию по конкретному стандарту. Сам список поддерживаемых стандартов есть в начале статьи.

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

Файловый менеджер

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

В новом сервисе мы постарались к ним подготовиться. Для этого все файлы были разделены на 2 категории:

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

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

После разделения файлов на 2 категории тут же возникла другая проблема. Как передавать большие объемы данных между двумя языками?

Вот тут и возникла необходимость в файловом менеджере, который будет работать как в C, так и в GO-частях. Вообще, при проектировании файлового менеджера главная идея была в том, чтобы код в C и в GO-частях не заботился о том, где и как хранятся файлы. А мог просто по uuid из запроса получить требуемые ему данные. Например, подписанный файл или конкретный чанк неподписанного файла. При этом все это должно было работать одинаково на обеих сторонах сервиса.

Для этого файловый менеджер пришлось разделять на 2 части. Первая часть находится на стороне GO. Она умеет делать все необходимые операции, например, записывать или удалять файлы и чанки, собирать из чанков итоговый файл для отправки и т.д. Синхронизация между С и GO-частями также происходит на стороне GO, за счет возможности блокировать директорию по uuid перед вызовом C-кода.

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

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

Вот так, например, выглядит метод для парсинга многочанкового документа:

В примере вызовом xmlCreatePushParserCtxt создается контекст парсера, а для добавления в него чанков вызывается xmlParseChunk.

//Парсинг всех доступных чанковfor(int i = 1; i <= numberOfChunks; i++){    //Чтение данных из файла в буфер    result = fread(data, sizeof(char), fileSize, pFile);    if (result != fileSize)    {      res = GENERAL_ERROR;      errorLog(uuid, LOGGER_FILE_INFO, "fread failed");      goto err;    }    if(1 == i)    {        //Инициализация парсера        ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, sizeof(chars), filePath);        if (NULL == ctxt) {            res = XML_PARSE_ERROR;            errorLog(uuid, LOGGER_FILE_INFO, "xmlCreatePushParserCtxt failed");          goto err;        }            //Добавление поддержки больших файлов        res = xmlCtxtUseOptions(ctxt, XML_PARSE_HUGE);        if(OK != res)        {           res = XML_PARSE_ERROR;            errorLog(uuid, LOGGER_FILE_INFO, "xmlCtxtUseOptions failed");            goto err;        }        //Парсинг первого чанка        res = xmlParseChunk(ctxt, data + sizeof(chars), fileSize - sizeof(chars), 0);        if(OK != res)        {          res = XML_PARSE_ERROR;            errorLog(uuid, LOGGER_FILE_INFO, "xmlParseChunk failed");            goto err;        }        if (1 != ctxt->wellFormed || 0 != ctxt->errNo)        {           res = XML_PARSE_ERROR;           formatErrorLog(uuid, LOGGER_FILE_INFO, "xmlParseChunk failed, wellFormed: %d, err: %d", ctxt->wellFormed, ctxt->errNo);           goto err;        }    }    else if(numberOfChunks == i)    {        //Парсинг последнего чанка        res = xmlParseChunk(ctxt, data, fileSize, 1);        if(OK != res)        {          res = XML_PARSE_ERROR;            errorLog(uuid, LOGGER_FILE_INFO, "xmlParseChunk failed");            goto err;        }        if (1 != ctxt->wellFormed || 0 != ctxt->errNo)        {          res = XML_PARSE_ERROR;          formatErrorLog(uuid, LOGGER_FILE_INFO, "xmlParseChunk failed, wellFormed: %d, err: %d", ctxt->wellFormed, ctxt->errNo);           goto err;        }             //Возвращение ссылки на распаршеный документ        *doc = ctxt->myDoc;    }    else    {       //Парсинг промежуточных чанков        res = xmlParseChunk(ctxt, data, fileSize, 0);        if(OK != res)        {            res = XML_PARSE_ERROR;            errorLog(uuid, LOGGER_FILE_INFO, "xmlParseChunk failed");            goto err;        }        if (1 != ctxt->wellFormed || 0 != ctxt->errNo)        {            res = XML_PARSE_ERROR;            formatErrorLog(uuid, LOGGER_FILE_INFO, "xmlParseChunk failed, wellFormed: %d, err: %d", ctxt->wellFormed, ctxt->errNo);            goto err;        }    }}

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

Ниже пример метода для дампа xml в несколько чанков.

В примере из метода dumpXmlDocAndSplitIntoChunks вызывается метод xmlOutputBufferCreateIO, позволяющий создать буфер, при заполнении которого будут вызываться переданные нами callback-и dumpToFileWriteCallback и dumpToFileCloseCallback. В которые первым параметром будет приходить переданный нами контекст callbackData. Для простоты пример с dumpToFileCloseCallback и writeToFile я приводить не стал.

Впоследствии этот буфер передается в метод xmlNodeDumpOutput, который и будет заниматься дампом.

static error_code dumpXmlDocAndSplitIntoChunks(const char *uuid, xmlDocPtr *doc, const char *tempDirPath, int *signedXmlLen, const char *chunkType, const int chunkSizeInByte, int *numberOfChunks){xmlNodePtr             rootNode = NULL;xmlOutputBufferPtr              buf = NULL;dumpToFileWriteCallbackTempData callbackTempData = {0};dumpToFileWriteCallbackData     callbackData = {uuid, tempDirPath, chunkType, chunkSizeInByte, numberOfChunks, signedXmlLen, &callbackTempData};int                    bufRes = 0;error_code            res = OK;debugLog(uuid, LOGGER_FILE_INFO, "started");if(NULL == doc || NULL == tempDirPath || NULL == signedXmlLen || NULL == chunkType || 0 == chunkSizeInByte || 0 == numberOfChunks){res = BAD_INPUT;errorLog(uuid, LOGGER_FILE_INFO, "bad input");goto err;}rootNode = xmlDocGetRootElement(*doc);if(NULL == rootNode){res = GENERAL_ERROR;errorLog(uuid, LOGGER_FILE_INFO, "xmlDocGetRootElement failed");goto err;}buf = xmlOutputBufferCreateIO(dumpToFileWriteCallback, dumpToFileCloseCallback, &callbackData, NULL);if(NULL == buf){res = GENERAL_ERROR;errorLog(uuid, LOGGER_FILE_INFO, "xmlOutputBufferCreateIO failed");goto err;}xmlNodeDumpOutput(buf, *doc, rootNode, 0, 0, NULL);bufRes = xmlOutputBufferFlush(buf);if(0 > bufRes){res = GENERAL_ERROR;errorLog(uuid, LOGGER_FILE_INFO, "xmlOutputBufferFlush failed");goto err;};err:if(NULL != buf)xmlOutputBufferClose(buf);return res;}

Пример определения структур dumpToFileWriteCallbackTempData и dumpToFileWriteCallbackData:

typedef struct{    char   *tempBuf;    int    tempBufLen;    int    tempBufSize;    bool   tempBufAllocated;} dumpToFileWriteCallbackTempData;typedef struct{    const char                       *uuid;    const char                       *tempDirPath;    const char                       *chunkType;    const int                        chunkSizeInByte;    int                              *numberOfChunks;    int                              *signedXmlLen;    dumpToFileWriteCallbackTempData  *tempData;} dumpToFileWriteCallbackData;

Пример dumpToFileWriteCallback:

static int dumpToFileWriteCallback(void *context, const char *buffer, int len){    dumpToFileWriteCallbackData *callbackData = (dumpToFileWriteCallbackData*) context;    dumpToFileWriteCallbackTempData *tempData = (dumpToFileWriteCallbackTempData*) callbackData->tempData;    int internalLen = 0;    error_code res = OK;    //Вызывается при флуше    if(len <= 0 && tempData->tempBufSize != 0)    {        //Запись данных в файл        res = writeToFile(callbackData->uuid, callbackData->tempDirPath, callbackData->chunkType, *callbackData->numberOfChunks + 1, tempData->tempBuf, tempData->tempBufSize);        if(OK != res)        {            errorLog(callbackData->uuid, LOGGER_FILE_INFO, "writeToFile failed");            return -1;        }        *callbackData->numberOfChunks += 1;        *callbackData->signedXmlLen += tempData->tempBufSize;        tempData->tempBufSize = 0;        return len;    }    if(len <= 0)        return len;    if(!tempData->tempBufAllocated)    {        tempData->tempBuf = (char*) malloc(callbackData->chunkSizeInByte);        if(NULL == tempData->tempBuf)        {        errorLog(callbackData->uuid, LOGGER_FILE_INFO, "malloc failed");            return -1;        }        tempData->tempBufLen = callbackData->chunkSizeInByte;        tempData->tempBufSize = 0;        tempData->tempBufAllocated = true;        *callbackData->numberOfChunks = 0;        *callbackData->signedXmlLen = 0;    }    internalLen = len;    while(0 != internalLen)    {        if(internalLen == tempData->tempBufLen)        {            //Запись данных в файл            res = writeToFile(callbackData->uuid, callbackData->tempDirPath, callbackData->chunkType, *callbackData->numberOfChunks + 1, buffer, len);            if(OK != res)            {                errorLog(callbackData->uuid, LOGGER_FILE_INFO, "writeToFile failed");                return -1;            }            internalLen -= len;            *callbackData->numberOfChunks += 1;            *callbackData->signedXmlLen += len;            break;        }        if(tempData->tempBufSize == tempData->tempBufLen)        {            //Запись данных в файл            res = writeToFile(callbackData->uuid, callbackData->tempDirPath, callbackData->chunkType, *callbackData->numberOfChunks + 1, tempData->tempBuf, tempData->tempBufSize);            if(OK != res)            {                errorLog(callbackData->uuid, LOGGER_FILE_INFO, "writeToFile failed");                return -1;            }            *callbackData->numberOfChunks += 1;            *callbackData->signedXmlLen += tempData->tempBufSize;            tempData->tempBufSize = 0;        }        int freeSpaceInBuf = tempData->tempBufLen - tempData->tempBufSize;        if( internalLen < freeSpaceInBuf )        {            memcpy(tempData->tempBuf + tempData->tempBufSize, buffer, internalLen);            tempData->tempBufSize +=internalLen;            internalLen = 0;            break;        }        else if( internalLen > freeSpaceInBuf )        {            memcpy(tempData->tempBuf + tempData->tempBufSize, buffer, freeSpaceInBuf);            tempData->tempBufSize +=freeSpaceInBuf;            buffer += freeSpaceInBuf;            internalLen -= freeSpaceInBuf;            continue;        }    }    return len;}

Самое главное, что позволяет этим двум частям обходится без передачи какой-либо информации, кроме uuid и количества чанков это универсальные имена для файлов.

Например, когда первый чанк файла приходит на подписание, то он будет называться unsignedXmlChunk_1 (если файл передавался без разбиения на чанки, то он будет файлом, состоящим из одного чанка) и будет лежать в директории, имя которой это uuid из запроса. Соответственно, когда из GO части вызывается метод для подписания запроса, в который предается количество чанков и uuid, то C-часть точно может сказать файловому менеджеру какие файлы ей нужны для подписания. Когда итоговый подписанный файл нужно записать на диск, он будет сохраняться в ту же директорию, но уже с именем signedXmlChunk_1. И GO-часть при отсутствии ошибок будет пытаться вернуть именно этот файл в качестве ответа.

Вот так, например, выглядит метод для добавления чанков для файлов разных типов:

func AddSignedXmlChunk(uuid string, tempDirPath string, chunkName string, content io.Reader, lockDir bool) (err error) {return addChunk(uuid, tempDirPath, "signedXmlChunk_"+chunkName, content, lockDir)}func AddUnsignedXmlChunk(uuid string, tempDirPath string, chunkName string, content io.Reader, lockDir bool) (err error) {return addChunk(uuid, tempDirPath, "unsignedXmlChunk_"+chunkName, content, lockDir)}func addChunk(uuid string, tempDirPath string, chunkName string, content io.Reader, lockDir bool) (err error) {file, err := os.OpenFile(filepath.Join(tempDirPath, chunkName), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)if err != nil {logger.Error.Println(uuid, err)return err}defer func() {defErr := file.Close()if defErr != nil {logger.Error.Println(uuid, defErr)err = defErr}}()n, err := io.Copy(file, content)if err != nil {logger.Error.Println(uuid, "Copy error", err, ", bytes written", n)return err}return err}

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

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

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

Заключение

Несмотря на то, что эта часть статьи получилась достаточно объемной, это только предисловие ко второй части статьи, которая выйдет в ближайшее время.

А пока буду рад обсудить все вышесказанное в комментариях.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru