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

Поиск сообщений в 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 ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

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

Межпланетная файловая система — Переходим на localhost (локальный шлюз IPFS)

Понедельник, 31 Июля 2017 г. 22:46 + в цитатник

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


Пользователю это даст быстрый доступ к его локальной копии нашего сайта.


Также мы научимся переключать на локальный шлюз IPFS сайты которые этого ещё не делают.


Напомню: InterPlanetary File System — это новая децентрализованная сеть обмена файлами (HTTP-сервер, Content Delivery Network). О ней я рассказывал в статье "Межпланетная файловая система IPFS".



image


Переключаем наш сайт


DNS


У нашего сайта на данный момент уже есть как минимум 3 DNS записи:


A    [Наш домен]             [IP адрес хостинга]
TXT  [Наш домен]             dnslink=/ipfs/[CID контента]
TXT  _dnslink.[Наш домен]    dnslink=/ipfs/[CID контента]

Добавим к ним ещё 3:


A    l.[Наш домен]             127.0.0.1
TXT  l.[Наш домен]             dnslink=/ipfs/[CID контента]
TXT  _dnslink.l.[Наш домен]    dnslink=/ipfs/[CID контента]

[CID контента] — Это идентификатор контента (CID) раньше назывался мультихеш. Его мы получаем публикуя сайт командой ipfs add в сети IPFS.


Скрипты и стили


В HTML тегах script и link появились поля integrity и crossorigin. Они отвечают за проверку хеша до запуска скрипта или применения стилей. Их мы и используем для определения рабочего шлюза у посетителя сайта.




Расположить их лучше в конце страницы чтоб они не задерживали загрузку и отображение.


Варианты адресов которые нам надо проверить:


  1. http://l.[наш домен]:8080
    8080 это стандартный порт на котором по умолчанию запускается IPFS.
    Если всё настроено правильно то с http версии сайта браузер загрузит скрипт или стиль.


  2. https://l.[наш домен]:8443
    8443 это порт на который пользователь может настроить stunnel.
    Данный вариант нам понадобится если запрос идёт с HTTPS сайта и наш домен добавлен в локальный сертификат.


  3. http://127.0.0.1:8080/ipns/[наш домен]
    Этот вариант на случай если мы не задали l домен для нашего сайта и запрос идёт с http.


  4. https://127.0.0.1:8443/ipns/[наш домен]
    Этот вариант на случай запроса с https. Этот вариант сработает если не задан l домен или не добавлен в локальный сертификат.

Аналогичным образом мы можем проверить порты 80 для http и 443 для https.


Проверяем локальный шлюз и переключаемся на него скриптом


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



redirect_to_ipfs_gateway.js


var redirect_to_local;

/*
Эта функция добавляет к текущему домену третьим уровнем  домен ```l``` 
*/

function l_hostname()
{
    var l_hostname =  window.location.hostname.split(".");
    l_hostname.splice(-2,0,"l");
    return l_hostname.join(".");
}

/*
Эта функция создаёт новый элемент script и с адресом скрипта который должен загрузиться через локальный шлюз пользователя.

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

В случае не удачи выполниться функция из переменной onerror которая присваивается соответствующему полю элемента script.
*/

function add_redirect_script(prtocol, port, use_ip, onerror){
    var script = document.createElement("script");
    script.onerror = onerror;
    script.setAttribute("integrity", "sha384-dActyNwOxNY9fpUWleNW9Nuy3Suv8Dx3F2Tbj1QTZWUslB1h23+xUPillTDxprx7");
    script.setAttribute("crossorigin", "anonymous");
    script.setAttribute("defer", "");
    script.setAttribute("async", "");
    if ( use_ip )
        script.setAttribute("src", prtocol+"//127.0.0.1:"+port+"/ipns/"+window.location.hostname+"/redirect_call.js");
    else
        script.setAttribute("src", prtocol+"//"+l_hostname()+":"+port+"/redirect_call.js");

    redirect_to_local = function()
    {
        var a = document.createElement("a");
        a.href = window.location;
        a.protocol = prtocol;
        a.port = port;
        if ( use_ip ){
            a.pathname = "/ipns/" + a.hostname + a.pathname;
            a.hostname = "127.0.0.1";
        }else{
            var hostname = a.hostname.split(".");
            hostname.splice(-2,0,"l");
            a.hostname = hostname.join(".");
        }
        window.location = a.href;
    };
    document.head.appendChild(script);
}

/*
Это главная функция которая запускается сразу. Она проверяет не находимся ли мы уже по адресу шлюза. Если нет то начинает проверять его доступность перебирая варианты адресов и протоколов.
*/
!function(location){
    if ( location.protocol.indexOf("http") == 0 &&
         location.hostname.length          >  0 &&
         location.hostname.indexOf("l.")   != 0 &&
         location.hostname.indexOf(".l.")  <  0 &&
         location.hostname                 != "127.0.0.1" ) 
    {   
        add_redirect_script( "http:",  8080, false,
         function(){
            add_redirect_script( "https:", 8443, false,
             function(){
                add_redirect_script( "http:", 8080, true, 
                 function(){
                    add_redirect_script( "https:", 8443, true );
                 } );
             } );
         } );
    }
}(window.location)

В пару ему идет скрипт:
redirect_call.js (sha384-dActyNwOxNY9fpUWleNW9Nuy3Suv8Dx3F2Tbj1QTZWUslB1h23+xUPillTDxprx7)


redirect_to_local();

Integrity этого скрипта можно посчитать командой:


openssl dgst -sha384 -binary < "redirect_call.js" | openssl enc -base64 -A

У меня соответственно результат этой команды:


dActyNwOxNY9fpUWleNW9Nuy3Suv8Dx3F2Tbj1QTZWUslB1h23+xUPillTDxprx7

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


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


Определяем рабочий шлюз при помощи CSS


  1. Создадим CSS файл который будет маяком работы шлюза.


    httpl.css (sha384-9LLp4PYTHwNvd5whc7IOL6JLDJ4aoPufAFts3rMLZOg5b//BLQZTfe7krAzWAm+a)


    .httpl{display: block;}

  2. Скрываем элементы страницы которые будут показаны только при доступности локального шлюза.



  3. Добавляем CSS маяк в конце страницы



  4. Этот элемент будет отображаться если загрузиться "httpl.css"

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


Локализуем глобальный шлюз или сайты в IPFS


Возьмём к примеру глобальный IPFS шлюз gateway.ipfs.io и перенаправим этот адрес на наш локальный IPFS шлюз.


Условие: У нас уже установлен и работает на стандартном порту 8080 IPFS шлюз.


  1. В файл hosts добавляем домен который хотим загружать с IPFS шлюза.


    127.0.0.1 gateway.ipfs.io

  2. Устанавливаем и настраиваем Stunnel.
    stunnel.conf:


    ; Открываем дополнительный защищённый порт шлюза для того чтобы сайты могли сами на него переключиться
    
    [https gateway]
    accept  = 127.0.0.1:8443
    connect = 127.0.0.1:8080
    cert = stunnel.pem
    TIMEOUTclose = 0
    
    ; Открываем стандартный порт 443 для HTTPS
    
    [https]
    accept  = 127.0.0.1:443
    connect = 127.0.0.1:8080
    cert = stunnel.pem
    TIMEOUTclose = 0
    
    ; Открываем стандартный порт 80 для HTTP
    
    [http]
    client = yes
    accept  = 127.0.0.1:80
    connect = 127.0.0.1:443

    Таким образом мы открываем 3 дополнительных порта (433, 8443, 80) которые подключают клиента к шлюзу IPFS.


  3. Создаём сертификаты и ключи.


    3.1. В директорию c конфигом копируем makecert.cmd


    echo off
    %~d0
    cd %~p0
    set STUNNELBIN = ..\bin
    set PATH=%STUNNELBIN%;%PATH%;
    
    rem // Первый вызов openssl создаст ключ и корневой сертификат в формате PEM
    rem // openssl попросит пользователя задать пароль которым будет защищён ключ и при каждой новой подписи сертификата шлюза этот пароль потребуется
    
    rem // Второй вызов openssl конвертирует сертификат из PEM в DER формат понятный Windows
    rem // Корневой сертификат в PEM формате понадобиться для Firefox
    
    if not exist "rootkey.pem" (
    echo [ req ]                                             >openssl.root.cnf
    echo distinguished_name = req_distinguished_name         >>openssl.root.cnf
    
    echo [v3_ca]                                             >>openssl.root.cnf
    echo subjectKeyIdentifier = hash                         >>openssl.root.cnf
    echo authorityKeyIdentifier = keyid:always,issuer:always >>openssl.root.cnf
    echo basicConstraints = critical, CA:TRUE                >>openssl.root.cnf
    echo keyUsage = keyCertSign, cRLSign                     >>openssl.root.cnf
    
    echo [ req_distinguished_name ]                          >>openssl.root.cnf
    
    openssl.exe req -newkey rsa:4096 -x509 -sha256 -days 5480 -config openssl.root.cnf -extensions v3_ca -utf8 -subj "/CN=127.0.0.1" -out rootcert.pem -keyout rootkey.pem
    
    openssl.exe x509 -outform der -in rootcert.pem -out rootcert.crt
    
    del openssl.root.cnf
    )
    
    rem // Теперь создаём ключ который будет использоваться шлюзом
    
    if not exist "gatewaykey.pem" (
    openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out gatewaykey.pem
    )
    
    rem // Делаем запрос сертификата шлюза
    
    if not exist "gateway.csr" (
    
    echo [ req ]                                                      >openssl.req.cnf
    echo req_extensions = v3_req                                      >>openssl.req.cnf
    echo distinguished_name = req_distinguished_name                  >>openssl.req.cnf
    
    echo [ req_distinguished_name ]                                   >>openssl.req.cnf
    
    echo [ v3_req ]                                                   >>openssl.req.cnf
    echo basicConstraints = CA:FALSE                                  >>openssl.req.cnf
    echo keyUsage = nonRepudiation, digitalSignature, keyEncipherment >>openssl.req.cnf
    
    openssl req -new -key gatewaykey.pem -days 1096 -batch  -utf8 -subj "/CN=127.0.0.1" -config openssl.req.cnf -out gateway.csr
    
    del openssl.req.cnf
    )
    
    rem // Если это не первое выполнение данного скрипта то в index.txt может храниться индекс следующей DNS записи.
    
    if exist "index.txt" (
    set /p index=/ Мы создаём openssl.cnf один раз и в дальнейшем дополняем его новыми доменами.
    
    if not exist "openssl.cnf" (
    
    echo basicConstraints = CA:FALSE   >openssl.cnf
    echo extendedKeyUsage = serverAuth >>openssl.cnf
    echo subjectAltName=@alt_names     >>openssl.cnf
    echo [alt_names]                   >>openssl.cnf
    echo IP.1 = 127.0.0.1              >>openssl.cnf 
    echo DNS.1 = localhost             >>openssl.cnf 
    
    set index=2
    del "index.txt"
    )
    
    rem // В цикле добавляем в openssl.cnf домены которые заданы в командной строке либо будут введены пользователем.
    
    :NEXT
    set /a aindex=%index% + 1
    set /a bindex=%index% + 2
    
    set domain=%1
    
    if !%domain% == ! (
    set /p domain=enter domain name or space:
    )
    
    if not !%domain% == ! (
    echo DNS.%index% = %domain%    >>openssl.cnf
    echo DNS.%aindex% = *.%domain% >>openssl.cnf
    
    echo %bindex% >index.txt
    
    set index=%bindex%
    shift
    goto NEXT
    )
    
    del gateway.pem
    
    rem // Создаём сертификат IPFS шлюза 
    
    openssl x509 -req -sha256 -days 1096 -in gateway.csr -CAkey rootkey.pem -CA rootcert.pem -set_serial %RANDOM%%RANDOM%%RANDOM%%RANDOM% -extfile openssl.cnf -out gateway.pem
    
    rem // Записываем ключ и сертификат в stunnel.pem который по умолчанию используется программой stunnel
    
    copy /b gateway.pem+gatewaykey.pem stunnel.pem
    
    rem // Даём пользователю прочитать ошибки или информацию
    
    pause

    3.2. Запускаем


    makecert.cmd ipfs.io

    При первом запуске данного скрипта будет создан корневой сертификат (rootcert.pem для firefox и rootcert.crt для остальных) ключ которому надо задать пароль. Корневой сертификат надо добавить в хранилище доверенных корневых сертификатов в браузере и операционной системе.


    Далее автоматически будет создан сертификат для шлюза которому надо задать домены которые он будет обслуживать.


  4. Пере/Запускаем stunnel


    reload.cmd


    echo off
    %~d0
    cd %~p0
    set STUNNELBIN = ..\bin
    set PATH=%STUNNELBIN%;%PATH%;
    stunnel -install -quiet
    stunnel -start -quiet
    stunnel -reload -quiet


Теперь gateway.ipfs.io будет работать на локальном шлюзе. Аналогично можно поступить с любым сайтом который размещён в IPFS.


Сайт для теста: ivan386.tk


Другие мои статьи о "межпланетной файловой системе":


  1. "Межпланетная файловая система IPFS"
  2. Публикуем сайт в межпланетной файловой системе IPFS
  3. Хостим сайт в межпланетной файловой системе IPFS под Windows
  4. Больше нет необходимости копировать в сеть
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334584/


Метки:  

Использование вулканизации для polymer-модулей

Понедельник, 31 Июля 2017 г. 22:36 + в цитатник
polymerjs vulcanize

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

Поэтому предлагаю вашему вниманию один из подходов для оптимизации polymer-виджетов.



Содержание:


1. Описание проблемы
2. Какие сложности возникают?
3. Как их можно решить?
4. Библиотека vulcanize-polymer-module
4.1. Структура
4.2. Описание bower.json
4.3. Описание package.json
4.3.1. Установка утилит
4.3.2. Настройка RollupJS
4.4. vulcanize-utils.js
5. Выводы

1. Описание проблемы


Одним из главных проблем polymer-приложений — это множественная подгрузка, используемых компонентов и всех зависимых компонентов, которые в свою очередь, могут состоять из вспомогательных стилей, behavior-ов, подгружаемых так же дополнительно. В результате консоль в network-разделе будет «засыпана» данными файлами. В виду всего этого, первая загрузка такого виджета может быть достаточно долгой, в зависимости от количества используемых составных вэб-компонентов.
Для этих целей в polymer-приложениях применяется вулканизация. Подразумевается, что у данного приложения, есть точка входа в виде, например, index.html, в котором разворачивается главный компонент-контейнер, например . В данном файле подключается само ядро polymer и файл компонента-контейнера и далее иерархически подключаются все используемые компоненты, которые сами являются отдельными html-файлами. Сам процесс вулканизации заключается в «склеивании» всех используемых компонентов и ядра polymer в один файл, который и будет в итоге являться точкой входа для index.html.

2. Какие сложности возникают?


  1. Первой сложностью является то, что у меня не polymer-приложение, а несколько составных компонентов(назовем их умными компонентами — УК), которые обернуты в виджет системы, то есть нет единой точки входа.
  2. Второй сложностью — что в течении работы с приложением может быть и не вызвана вовсе страница с данными виджетами, и соответсвенно ни один из polymer-компонентов будет просто не нужен в текущей сессию работы, не говоря уж о самом ядре polymer.
  3. Третьей — один УК использует один набор атомарных (paper-, iron- и другие) компонентов(назовем их глупыми компонентами — ГК), а другой — другой набор. Причем могут быть пересечения, то есть два разных УК используют одни и те же ГК.


3. Как их можно решить?


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

4. Библиотека vulcanize-polymer-module


Все выше сказанные тезисы я оформил в виде библиотеки vulcanize-polymer-module.
Хочу рассказать о ней подробней.

4.1. Структура

vulcanize-polymer-module/
+-- imports.html
+-- vulcanize-utils.js
+-- rollup.config.js
+-- bower.json
+-- package.json


4.2. Описание bower.json

В нем мы описываем все ГК, которые нам необходимы, как зависимости, включая так же само ядро polymer.
Например, раздел dependencies может выглядеть так:
dependencies
"dependencies": {
    "polymer": "Polymer/polymer#^2.0.0",
    "polymer-redux": "^1.0.0",
    "iron-flex-layout": "PolymerElements/iron-flex-layout#^2.0.0",
    "paper-button": "PolymerElements/paper-button#^2.0.0",
    "paper-badge": "PolymerElements/paper-badge#^2.0.0",
    "paper-icon-button": "PolymerElements/paper-icon-button#^2.0.0",
    "paper-input": "PolymerElements/paper-input#^2.0.0",
    "paper-item": "PolymerElements/paper-item#^2.0.0",
    "paper-checkbox": "PolymerElements/paper-checkbox#^2.0.0",
    "paper-tabs": "PolymerElements/paper-tabs#^2.0.0",
    "paper-listbox": "PolymerElements/paper-listbox#^2.0.0",
    "iron-a11y-keys": "PolymerElements/iron-a11y-keys#^2.0.0",
    "iron-list": "PolymerElements/iron-list#^2.0.0",
    "iron-icons": "PolymerElements/iron-icons#^2.0.0",
    "paper-progress": "PolymerElements/paper-progress#^2.0.0",
    "vaadin-split-layout": "vaadin/vaadin-split-layout#^2.0.0",
    "vaadin-grid": "^3.0.0",
    "iron-pages": "PolymerElements/iron-pages#^2.0.0",
    "iron-collapse": "PolymerElements/iron-collapse#^2.0.0",
    "iron-overlay-behavior": "PolymerElements/iron-overlay-behavior#^2.0.0",
    "vaadin-context-menu": "^3.0.0"
  }




Так как я совместно с polymer использую redux, я включил библиотеку polymer-redux.

4.3. Описание package.json

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

scripts
"scripts": {
    "build": "rollup -c",
    "vulcanize": "vulcanize imports.html  --inline-scripts --inline-css --strip-comments",
    "run-vulcanize": "npm run vulcanize > imports.vulcanize.html",
    "vulcanized": "vulcanize imports.html  --inline-scripts --inline-css --strip-comments | crisper  --html imports.vulcanized.html --js imports.vulcanized.js > imports.vulcanized.html",
    "html-minifier": "html-minifier imports.vulcanized.html --remove-optional-tags --collapse-whitespace --preserve-line-breaks -o imports.vulcanized.min.html",
    "build-all": "npm run vulcanized && npm run build && npm run html-minifier"
  }


Команды, в порядке и приоритетности их использования:
  • build-all — основная команда, которая и запускает весь процесс вулканизации.
  • vulcanized — выполняет саму вулканизация, то есть объединение всех компонентов и ядра в один файл, затем разбивает всю сборку отдельно на .js и .html файлы.(Используется утилита vulcanize и crisper)
  • build — очистка кода js-файла от комментариев.(используется RollupJS)
  • html-minifier — минификация html-файла.(используется html-minifier)


4.3.1. Установка утилит

Как видим используется множество дополнительных утилит, которые нам необходимо предварительно установить в систему.
Установка утилит
npm install -g vulcanize
npm install -g crisper
npm install -g html-minifier


4.3.2. Настройка RollupJS

Так как rollup используется только для очистки js-кода я использую только один плагин к нему- rollup-plugin-cleanup. Плагин rollup-plugin-progress используется для визуализации процесса сборки.
rollup.config.js
import progress from 'rollup-plugin-progress';
import cleanup from 'rollup-plugin-cleanup';

export default {
	entry: 'imports.vulcanized.js',
	dest: 'imports.vulcanized.js',

	plugins: [
		cleanup(),
		progress({
		}),
	]
};


4.4. vulcanize-utils.js

Для решения второго требования был написан утилитарный метод loadVulcanized, который загружает УК, но перед этим загружает вулканизированный файл, причем делает это один раз и в случаи повторного вызова загружает только сам УК.
Рассмотрим подробнее его параметры.
loadVulcanized = function(url, urlVulcanized, controller, html, store)
  • url — путь к умному компоненту. Является обязательным.
  • urlVulcanized -путь к вулканизированной сборке. По умолчанию — путь к данной сборке — ../vulcanize-polymer-module/imports.vulcanized.min.html
  • controller — в моем случаи это контроллер системного виджета. Опционально.
  • html — html-объект умного компонента. Имеет смысл, если задан контроллер.
  • store — redux store. Опционально.



5. Выводы


Конечно можно использовать polymer-cli с параметром build, но при сборке с его помощью подразумевается, что билдится polymer-проект, а так как мы используем компоненты не в одном контейнере , то собирать каждый УК придется по отдельности и файлы сборки будут иметь дублирование ядра polymer и составных ГК. Поэтому подход, описанный в статье имеет достаточную эффективность в системах использующих несколько UI-библиотек совместно, за счет единой точки входа для всех УК на базе polymer.
Один из возможных минусов можно рассмотреть, как избыточность ГК в вулканизированном файле, так как в нем собраны все ГК всех УК, используемых
в системе, но при этом не все УК могут быть использованы за сессию работы, в виду чего не все ГК будут использованы в загруженом вулканизированном файле.
Так же, как небольшое неудобство, можно рассмотреть тот факт, что после добавления нового компонента необходимо запустить сборку заново, после чего сделать обновление репозитория(push), а другие пользователи должны обновить данную библиотеку через bower update.
Несмотря на все это данная библиотека решает свою задачу в данном проекте, а значит может пригодится и кому-то еще.
Так что форкайте, милости просим.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333660/


Метки:  

Oracle Storage Cloud Services - все, в чем нуждается корпоративное хранилище данных

Понедельник, 31 Июля 2017 г. 19:40 + в цитатник
Корпоративные хранилища данных на основе публичного облака и относящиеся к ним сервисы уже достаточно проверены клиентами. Они отвечают их потребностям в хранении данных, предоставлении доступа и защите. В исполнении Oracle безопасное, эластичное и простое использование этих данных доступно в любой момент и в любой окружающей IT среде, связанной с интернетом.





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

В данной статье описаны сервисы хранения данных, их отличия от других систем данного класса, структура архитектуры решения и практический пример использования Oracle Database Cloud Service.

1. Oracle Storage Cloud Service - Object Storage

Это решение обеспечивает хранение наборов данных любого типа, включая структурированные и неструктурированные данные. Копию данных можно хранить в локальных (on-premise) системах, переместить ее в облако, или использовать совместно.

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

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

Простое управление на основе OpenStack и RESTful API упрощает интеграцию, освобождая ресурсы для других облачных проектов. Оплата «Pay-as-You-Go» и подписные модели уменьшают затраты по сравнению с долгосрочными контрактами.

Шифрование данных клиента, объединенное с дополнительным шифрованием в информационном центре Oracle, обеспечивает многоуровневую защиту информации. На стороне клиента шифрование выполняется Oracle Storage Cloud Software Appliance и Java SDK. Все перемещаемые данные шифруются на уровне SSL (Secure Sockets Layer). Уровень доступа пользователей и администраторов к данным осуществляется на контейнерном уровне.

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

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

Стандарты разработки - RestAPI на Open Stack Swift, Java Applications (File Transfer Manager и Java API), Shell scripting (Upload CLI).

Решение имеет «одиннадцать девяток надежности», т.е., обеспечивается 99,999999999%-ная надежность за счет поддержки нескольких копий каждого объекта на различных устройствах. Периодическая целостность данных сверяется средствами самовосстановления.

Oracle Storage Cloud Software Appliance - простое в использовании облачное хранилище данных с NFS совместимым интерфейсом POSIX. Является шлюзом NAS к облаку.

2. Oracle Storage Cloud Service - Archive Storage

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

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

К основным преимуществам решения можно отнести максимальное упрощение операций датацентра, низкие капитальные вложения и эксплуатационные расходы за счет меньшей потребляемой мощности и требований к системам охлаждения. Практикуется также оплата «Pay-as-You-Go» и подписные модели, а общая плата за хранение архивов - самая низкая среди аналогов ($12/TB в год).

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

3. Oracle Database Backup Service

Сервис защиты данных спроектирован для уникальных потребностей клиентов Oracle Database, с прямой интеграцией с RMAN (Oracle Recovery Manager). Таким образом, можно использовать в своих интересах основанную на облаке защиту данных в IT процессах.

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

Резервные копии подвергаются компрессии перед перемещением в облако. Емкость хранилища данных изменяется по требованию простым кликом по кнопке. Управление данными включает ролевое управление доступом и автоматическое удаление данных по указываемым критериям (за давностью хранения и пр.).

4. Oracle Storage Cloud Software Appliance

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

Решение имеет привычный, хорошо понятный интерфейс. Обеспечивается соединение приложений с облачным хранилищем данных на основе протокола NFS, а также совместимость с POSIX и Unix/Linux NFS клиентами. Реализовано автоматическое преобразование данных, - интерфейс позволяет работу с данными как с файлами, в то время как они сохраняются в облаке как объекты.

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

Доступность данных обеспечивается за счет регулярного резервного копирования метаданных в облако, а производительность на уровне локальной NAS - буферным кэшированием. Кэш можно конфигурировать в соответствии с рабочими нагрузками. Алгоритм Least Recently Used (LRU) сохраняет в кэше актуальные данные и удаляет ненужные.

Для управления используется технология N-Way Management (N-Way - телекоммуникационный протокол для управления сетевыми устройствами Ethernet и данными пользователя). Возможна работа из Admin UI на основе браузера, или интерфейса командной строки (Command Line Interface). Для автоматизации могут применяться скрипты. REST API, которые обеспечивает множественное развертывание из единого централизованного местоположения.

5. Oracle Public Cloud Data Transfer Services

Сервисы обеспечивают быструю передачу первоначального набора данных в облако и формирование хранилища данных в публичном облаке Oracle. Это - самый быстрый способ начать работать с данными в облаке. Сервис Storage Appliance Import надежно переносит большие наборы данных (исторические архивы, озера данных, большие базы наследуемых данных). За один раз может быть перемещено до 400 TB данных.

При этом нет необходимости платить за построение более быстрой сети для одноразовой «оптовой» передачи данных. Передача безопасна, поскольку трафик шифруется по стандарту AES-256. Для загрузки данных объектного или архивного хранилища используется простой стандартный интерфейс NFSv3. Одновременно формируется многорядная структура. Возможно также копирование данных внутри облака для создания реплики рабочей базы.

Oracle Storage Cloud Service и традиционные решения

Традиционные решения для хранения данных, как правило, имеют определенные проблемы с масштабируемостью, производительностью и управлением. Oracle Storage Cloud Service помогает их преодолеть.

1. В системе с непосредственно присоединенным устройством хранения (таким, как обычный жесткий диск в десктопе или ноутбуке), хранением данных, их поиском и упорядочиванием через файловую систему управляет ОС.

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

2. В сетевых устройствах хранения данных (Network-Attached Storage, NAS) аппаратные средства хранения физически отделены от серверов, на которых выполняются приложения. Устройства хранения при этом доступны как сетевые диски. Хранением данных, их поиском и упорядочиванием управляет сетевая файловая система (Network File System, NFS). Приложения, работающие на различных серверах, совместно используют NAS для хранения данных. Управление ресурсами хранения в локальной сети осуществляется централизованно.

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

3. Блочный принцип хранения используется в таких приложениях, как базы данных OLTP с высоким показателем скорости входных / выходных операций (Input / Output Operations per Second, IOPS). Это позволяет эффективно сохранять и восстанавливать данные в обход ОС, непосредственно взаимодействуя с виртуальными блочными устройствами.

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

Блочный принцип хранения оптимизирует системы хранения для IOPS и обеспечивает совместимые с POSIX файловые системы для Oracle Compute Cloud Service. Вместе с тем такой подход имеет ограничения с точки зрения масштабируемости и не поддерживает гранулирование метаданных.

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

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

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

Подытоживая перечисленное выше, следует отметить, что Oracle Storage Cloud Service обеспечивает недорогое, надежное, безопасное, и масштабируемое решение для хранения объектов. Это позволяет хранить неструктурированные данные и получать доступ к ним в любое время и отовсюду.

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

Краткий обзор архитектуры

Архитектура Oracle Storage Cloud Service очень доступна и избыточна. Она хорошо подходит для внешнего доступа, включая пользовательские приложения, Java SDK и REST клиенты.

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


Типовая архитектура Oracle Cloud Storage Service
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334578/


Метки:  

[Из песочницы] Как мы создали устройство быстрой обработки потока событий на FPGA

Понедельник, 31 Июля 2017 г. 19:32 + в цитатник
Устройство называется CEPappliance. CEP — от Complex Event Processing, а appliance — (и так должно быть понятно, но на всякий случай) “устройство” с английского.

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

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

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

Работая на “дядей” мы неплохо изучили технические аспекты и потребности торговли финансовыми инструментами на биржах и ориентировались в первую очередь на них. Это автоматизированная торговля (HFT, Algo Trading), контроль рисков (Pre-trade), организация “прямого” доступа к торгам (Direct Market Access) и т.п.

Но нам удалось сделать CEPappliance достаточно универсальным устройством, применимым в областях, где нужно прокачивать много данных и делать это не только быстро, но и с гарантированными малыми задержками. Благодаря встроенной поддержке стандартных сетевых протоколов и внесению минимальных задержек устройство применимо в телекоммуникациях для обнаружения нарушений безопасности в сетях, управления загрузкой сетей. Устройство может применяться в телематике, когда нужно за нескольких микросекунд принять решение и среагировать на поступление сигналов с датчиков. При этом логика обработки данных устройством может быть сложной. Для ее описания (программирования) мы используем некоторые приемы технологии Complex Event Processing (CEP).

CEPappliance было задумано и создавалось для решения задач, которые в упрощенном виде можно сформулировать так: с суммарной задержкой менее 3 микросекунд

  1. получить входные данные (сигнал) по сетевому интерфейсу в формате протоколов Ethernet, TCP/IP, UDP, FIX, FAST, TWIME (FIX SBE) и т.п.;
  2. разобрать и извлечь пользовательские данные;
  3. проанализировать пользовательские данные;
  4. сформировать выходные данные (реакцию) и отправить их по сетевому интерфейсу.

CEPappliance отличается от программного решения, выполняемого на архитектурах с центральным процессором тем, что ядром архитектуры устройства является программируемая пользователем вентильная матрица (ППВМ, FPGA), на которой реализованы все без исключения этапы решения описанных задач.

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

На FPGA мы разместили компоненты для разбора, извлечения, анализа входных данных и формирования выходных данных на одном кристалле образно говоря «без посредников» (см. рис. 4), которые обязательно присутствуют в решениях с центральным процессором.

Логическая схема традиционного решения с центральным процессором

Рис. 1. Логическая схема традиционного решения с центральным процессором

Логическая схема гибридного решения с центральным процессором и TCP Offload Engine на сетевой карте

Рис. 2. Логическая схема гибридного решения с центральным процессором и TCP Offload Engine на сетевой карте

Логическая схема гибридного решения с центральным процессором, TCP Offload Engine и реализацией протоколов прикладного уровня в сетевой карте

Рис. 3.Логическая схема гибридного решения с центральным процессором, TCP Offload Engine и реализацией протоколов прикладного уровня в сетевой карте

Логическая схема CEPappliance

Рис. 4. Логическая схема CEPappliance

В CEPappliance компоненты для разбора, извлечения, анализа входных данных и формирования выходных данных, расположены на кристалле FPGA и непосредственно взаимодействуют между собой.

Для этого пришлось “изобрести велосипед” заново. Напомню, что начинали мы работу (в далеком 2010 году) над CEPappliance в режиме хобби. Все делали сами “как надо и правильно”. В итоге, помимо прочего, мы реализовали Ethernet, TCP/IP, UDP, FIX и FAST “с нуля”.

Нам удалось создать эти компоненты так, что разбор входных данных происходит со скоростью их поступления (at wire speed). Компоненты реализуют соответствующие стандарты, которые “высечены в камне” и не часто изменяются. Для стандартных протоков мы предусмотрели механизм настройки. Например, модули протоколов FIX, FAST, TWIME и др. настраиваются с помощью параметров, задаваемых пользователем, и шаблонов или схем, описывающих структуру сообщений.

В то же время мы исходили из того, что (пользовательскте) алгоритмы обработки данных могут изменяться. Например, торговые стратегии или проверки, выполняемые брокером для минимизации рисков (pre-trade risk checks), следуют за изменением ситуации на рынке, модернизацией микроархитектуры биржи или требованиями регуляторов.

Разработка алгоритмов для FPGA непосредственно на “железячных” языках (VHDL, Verilog и т.п.) требует значительно больше времени для кодирования, отладки и тестирования, чем разработка на языках высокого уровня [2]. Для этого также необходимы специальные навыки, которыми программисты, пишущие программы на языках высокого уровня, как правило, не обладают. И если вы планируете использовать FPGA для ускорения выполнения своих алгоритмов, то вам придется передать детальное описание алгоритма FPGA-разработчику, который будет его реализовывать. Иногда это крайне нежелательно, так как передача описания алгоритма создает для его обладателя риск потери конкурентного преимущества.

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

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

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

Предоставив пользователю возможность самостоятельно программировать CEPappliance мы, очевидно, должны были предоставить и инструментарий для отладки этих программ. Без такого инструментария трудно будет воспользоваться всеми преимуществами CEPappliance. Поэтому мы разработали эмулятор устройства, который на 100% совместим с самим устройством. Отладив программу на эмуляторе, можно изменить конфигурацию (в большинстве случаев это смена IP адреса) и сходу запустить программу на устройстве.

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

А для автоматического тестирования пользовательских программ, написанных для CEPappliance, у нас есть специальный инструмент — Test Bench, который читает тестовые сценарии в табличном виде и выполняет их. Один и тот же набор тестов можно выполнять как с устройством, так и с его эмулятором.

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

Впереди много работы по развитию устройства, поиск клиентов в областях за пределами биржевой торговли и много новых идей!



1Про то, как образуется эта задержка в случае обмена данными по TCP/IP можно почитать в [1]. А здесь рассказано, как эту задержку можно уменьшить за счет реализации гибридной архитектуры с применением FPGA.

Ссылки


1. S. Larsen and P. Sarangam, “Architectural Breakdown of End-to-End Latency in a TCP/IP Network,” International Journal of Parallel Programming, Springer, 2009.
2. David F. Bacon, Rodric Rabbah, and Sunil Shukla. FPGA Programming for the Masses. ACM Queue, Vol 11(2), February 2013.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334574/


Метки:  

10 шагов настройки Create React App + TypeScript + Ant-Design

Понедельник, 31 Июля 2017 г. 19:00 + в цитатник

В какой-то момент борьбы со Flow-Type на VSCode, я согласился, что нужно переезжать на TypeScript. Поддержка Flow-Type обеспечивается сторонним плагином и совсем-совсем не устраивает. Если файл невалиден с точки зрения Flow-Type, то переходы внутри кода между файлами перестают работать, например. А возвращаться на WebStorm после знакомства с VSCode — я не могу себя заставить. Microsoft, как обычно, затягивает полностью. Любишь VSCode, получи TypeScript.


Если бы мне кто сказал год назад, что я вернусь в поклонники Microsoft — сложно было такое представить. Но случаются и более удивительные вещи. Я в полном восторге от качества китайского набора React-компонентов от Ant-Design. И хотя он написан на TypeScript, чтобы его прикрурить, нужен babel-plugin-import.


Но как же остаться на Create React App (CRA) — у форка для TypeScript (CRA-TS) выпилили Babel. Поддерживать собственную вариацию CRA представляется безумием. Многообещающий Preact-CLI (как замена CRA) не обеспечивает необходимый уровень совместимости с React. Но, играясь с Preact-CLI, заметил, что preact.config.js очень похож на react-app-rewired, которым я активно пользуюсь для обхода ограничений конфигурации Webpack в CRA. Сопоставил этот факт с идеей перевода CRA-TS c ts-loader на awesome-typescript-loader, внутри которого можно включить Babel. И вуаля!


0) установить create-react-app


$ npm install -g create-react-app

1) создать проект


$ create-react-app cra-ts-antd --scripts-version=react-scripts-ts
$ cd cra-ts-antd/

2) добавить пакеты


$ yarn add react-app-rewired react-app-rewire-less awesome-typescript-loader babel-core babel-plugin-import babel-preset-react-app -D

3) добавить config-overrides.js


module.exports = function override(config, env) {
  const tsLoader = config.module.rules.find(conf => {
    return conf.loader && conf.loader.includes('ts-loader')
  })
  tsLoader.loader = require.resolve('awesome-typescript-loader')
  tsLoader.query = {
    useBabel: true,
  }

  const tsLintLoader = config.module.rules.find(conf => {
    return conf.loader && conf.loader.includes('tslint-loader')
  })
  tsLintLoader.options = tsLintLoader.options || {}
  // FIXED Warning: The 'no-use-before-declare' rule requires type infomation.
  tsLintLoader.options.typeCheck = true

  const rewireLess = require('react-app-rewire-less')
  config = rewireLess(config, env)

  const path = require('path')
  // For import with absolute path
  config.resolve.modules = [path.resolve('src')].concat(config.resolve.modules)

  return config
}

4) изменить package.json; код подключает враппер react-app-rewired


  "scripts": {
-   "start": "react-scripts-ts start",
-   "build": "react-scripts-ts build",
+   "start": "BROWSER=none react-app-rewired start --scripts-version react-scripts-ts",
+   "build": "react-app-rewired build --scripts-version react-scripts-ts",
  }

5) изменить tsconfig.json; код включает настройки для абсолютного импорта, помимо прочего


{
  "compilerOptions": {
+   "allowSyntheticDefaultImports": true,
+   "baseUrl": ".",
+   "paths": {
+     "*": ["*", "src/*"]
+   },
-   "jsx": "react",
+   "jsx": "preserve",
  },
  "exclude": [
+   "config-overrides.js",
  ]
}

6) добавить .babelrc; код назначает требуемый пресет и подключает babel-plugin-import


{
  "presets": ["react-app"],
  "plugins": [
    ["import", { "libraryName": "antd", "style": false }]
  ]
}

7) добавить antd; версия фиксированная, т.к. в следующей версии 2.12.3 обнаружена ошибка


$ yarn add antd@2.12.2

8) добавить src/resources/main.less; код переопределяет переменную


@import "~antd/dist/antd.less"; // import official less entry file

@primary-color: #1DA57A;

9)… и подключить в index.tsx; импорт по абсолютному пути от src


+ import 'resources/main.less';

10) изменить App.tsx


  import * as React from 'react';
  import './App.css';
+ import { Button } from 'antd';

  const logo = require('./logo.svg');

  class App extends React.Component<{}, {}> {
    render() {
      return (
        
logo

Welcome to React

To get started, edit `src/App.tsx` and save to reload.

+
); } } export default App;

Исходники на GitHub


PS Ищу работу.

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

https://habrahabr.ru/post/334572/


Метки:  

Обновление инфраструктуры рабочих мест трейдеров

Понедельник, 31 Июля 2017 г. 18:43 + в цитатник
В 2014 году рабочее место трейдера Райффайзенбанка представляло из себя стол, под которым находилось скопление системных блоков с подключенными к ним мониторами, мышками и клавиатурами. Мало того, что на всем этом «зоопарке» было очень тяжело и неудобно работать, так и про хорошее и своевременное техническое обслуживание можно было забыть. Пыль, неизбежно образующаяся в открытых помещениях, повышенное тепловыделение от работающих станций значительно в разы сокращали срок службы оборудования.

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

2014 год:





Итак, мы поняли, что пора что-то менять, и сформулировали основные цели обновления инфраструктуры рабочих мест трейдеров в Райффайзенбанке:

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

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

Затем мы обратили внимание на комплексное решение от компании WEY, которое представляет собой комплекс аппаратных средств, который позволяет интегрировать различные системы: торговые, информационные, аналитические, риск-менеджмент системы управления, контроля, — и организовать современный трейдинг в банке, центр управления и контроля различными системами безопасности и сетями. Оно включало в себя интеграцию большого количества разнородных систем и источников данных на каждом рабочем месте. С его помощью можно было объединить Thomson Reuters Dealing (впоследствии FX Trading), Bloomberg, Moex (MICEX +RTS), Quik и многие другие трейдинговые площадки. Технологически же, WEY — платформа распределенного доступа, которая состоит из передающих и приемных интерфейсных карт, коммутационном сетевым оборудовании HUAWEY, управляющих консолей WEY, адаптированных и настроенных под определенные банковские торговые системы, а в качестве рабочих станций мы применили сервера HP семейства SL.

Решение WEY полностью аппаратное. Работает на технологии передачи данных по WEY протоколу, обеспечивающему real-time передачу данных с возможностью инкапсуляции в IP для передачи в режиме unicast и multicast. Связь компьютеров в техническом помещении с трейдинговым залом осуществляется на уровне интерфейсов компьютеров через платформу WEY, по отдельному специально организованному (выделенному) сегменту сети на базе проприетарного протокола WEY.

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

Сервера HP были выбраны не случайно. Их можно было поставить в стойку и тем самым сократить используемое место в техническом помещении. Однако изначально с ними возникла серьезная проблема: серверное оборудование не особенно предназначено для работы как полноценная рабочая станция, например поставлялось без видео и звуковых карт. Если с видеокартами особых проблем не возникло, мы использали профессиональные решения от Nvidia, то со звуком возникли проблемы – звуковую карту банально некуда было вставлять… Решение было найдено неожиданное — USB-карты, которые подключались и устанавливались внутри самих серверов в свободный USB порт на материнской плате.

Мультифункциональная клавиатура WEY MK 06

Вместо нескольких клавиатур и манипуляторов “мышь” на каждом рабочем месте была установлена одна мультифункциональная клавиатура WEY MK 06 на основе FPGA с клавишами и цветным ЖК дисплеем. Специальные встроенные блоки и функциональные клавиши в дисплее для работы с системами ведущих поставщиков информационно-аналитических и торговых сервисов позволяют значительно ускорить работу трейдера. Функциональные настройки клавиатуры и раскладки клавиш могут быть изменены персонально в зависимости от требований пользователя, максимально обеспечивая удобство работы при осуществлении ежедневных операций.

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

Безопасность
Очень важно было обеспечить безопасность решения. В трейдинговом зале могут находиться несколько различных систем от разных провайдеров, которые находятся в разных сетях. Некоторые из этих сетей могут быть публичными и небезопасными. С WEY нет необходимости устанавливать все системы в одну подсеть для того, чтобы пользователь мог комфортно работать со всем множеством систем. Платформа позволяет оставить каждую систему в своей подсети и работать пользователю со всеми необходимыми системами и быстро переключаться между ними. При этом все сети остаются изолированными, а система WDP работает в отдельном изолированном специально организованном сегменте. Для передачи сигналов используется специально разработанный проприетарный протокол WEY, инкапсулированный в IP для транспортировки через Ethernet для соединения карт sender-receiver применяются универсальные платы с SFP модулями, которые позволяют адаптировать систему под любую кабельную систему в зависимости от предпочтений клиента и технических ограничений ( требований ) объекта.
В системе предусмотрена возможность создания логинов и паролей для пользователей, логирование использования систем пользователями. В последних моделях клавиатур SmartTouch предусмотрены датчики “присутствия” пользователей на рабочем месте, которые позволяют автоматически «разлогинить» пользователя из системы, как только он покидает рабочее место. В этот момент все мониторы становятся темными.

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

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

• отсутствуют права доступа
• полный доступ
• только просмотровый режим
• приватный режим
• режим доступа только по разрешенным интерфейсам

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

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

2017 год:


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

https://habrahabr.ru/post/334570/


Метки:  

7 способов использования синего в цветах вашей фирмы

Понедельник, 31 Июля 2017 г. 18:27 + в цитатник
image

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



Классический синий


image
Источник

image
Источник

image
Источник

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

Это любимый цвет ай-ти индустрии и соцсетей, производителей электроники, автомобильных гигантов и банков. В качестве иллюстраций выступают компании с мировым именем: Twitter, Ford, Intel, Yota, Вконтакте, логотипы Windows 8, 10 и многие, многие, многие другие.
Логомашина, к примеру, использовала классический синий, чтобы создать легкий, воздушный стиль для компании Pixel. Мы опирались на принадлежность к ай-ти индустрии, открытость и профессионализм проекта. Несложные элементы округлой формы на большом расстоянии друг от друга и теплый оттенок синего дали нужный эффект.

image
Источник


Классический синий не вызывает отторжения. Он подойдет, если вы позиционируете свой проект как надежного партнера, которому можно доверять.
Это символ креативности, открытости и новизны. Дизайнер используют осветленность оттенка чтобы показать настроение компании — Ford использует более темный, Twitter наоборот — более светлый, легкий.

Синий + красный


image
Источник

image
Источник


image
Источник

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

Известные примеры легко найти в индустрии моторных масел: Mobil, Chevron, Valvoline. Любят двуколор и автопроизводители. Самый популярный пример сочетания синего и красного —PEPSI. В этом случае красный на втором месте. Упаковки и рекламные баннеры «пепси» — сочетание синего и белого.

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

Ментоловый


image
Источник

image
Источник


image
Источник


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

Кофейный бренд Matiz использует ментоловый как основной цвет, в сочетании с желтым, коричневым, красным и фиолетовым — в зависимости от сорта кофе.
Производитель натуральной косметики — 2b (Bio & Beauty) построил фирменный стиль на сочетании ментолового с темно-серым. Melez Tea, Made Coffee, создатели пользовательских интерфейсов Blank — используют его как фон. Для американских ювелиров Tiffany & Co ментоловый вообще стал настоящим культом.
Наши дизайнеры тоже выбрали ментоловый цвет, создавая фирменный стиль для продавцов мороженого Мятный Слон. Ассоциации с легкостью, свежестью и близостью к природе подошли компании лучше всего. Ментоловый дополняют соломенный и белый цвета, а также мелкие разноцветные элементы.

image
Источник

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

Ультрамарин


image
Источник

image
Источник

image
Источник

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

Компьютерный синий — это холодный и строгий цвет, с ним часто используют рубленый шрифт без засечек, который имеет общий размер для всех букв алфавита, он легко читается и называется гротескным.
Компьютерный синий чуть темнее классического, ненатуральный и очень насыщенный.
Его редко используют крупные компании, на ум приходит только Western Digital, зато небольшие креативные команды или агентства которые организуют концерты, создатели сайтов и программного обеспечения выделяются на общем фоне своими яркими синими «экранами».
Когда мы разрабатывали фирменный стиль для Digital Motion, то использовали холодный синий в сочетании с белым и голубыми оттенками.
Образ дополнили колоритные «системные» шрифты, создав ассоциацию с интерфейсом Windows.
image
Источник


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

Синий в основе, со многими


image
Источник

image
Источник

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

На основе этого сочетания выстраивают свой фирменный стиль туристические компании Eccentric Travels, Sankeo, студия анимации Gotham Pro Animated, финансисты Open View и многие другие
Интересным для наших дизайнеров была работа над фирменным стилем Crowd Back, платформы для создания опросов и голосований.
Образ строился на ассоциации с прямой речью и круговой диаграммой, намек на множество существующих мнений. Центральным элементом стиля стала буква “C”,
стилизованная под графический значок прямой речи.

image
Источник

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

Благородный синий


image
Источник


image
Источник

image
Источник

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

Темно-синий используют бренды, фундамент имиджа которых — качество продукции или изначально высокий статус. Королевский синий популярен среди государственных учреждений, банков, адвокатских контор, элитных гостиниц и ресторанов. Образы элитарности, строгости и оправданного консерватизма которые несет темно-синий цвет используются в качестве «демонстрации силы». Самые известные примеры: GAP, Nivea, Maserati.

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

image
Источник

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

Вывод


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

https://habrahabr.ru/post/334566/


Метки:  

[Из песочницы] Дефейс ask.mcdonalds.ru

Понедельник, 31 Июля 2017 г. 18:24 + в цитатник
Все началось, когда обычным майским днем я наткнулся на сообщество ВКонтакте, публикующее забавные вопросы людей о продукции Макдоналдс, которые они задавали на некоем сайте. Это выглядело примерно так:


Полистав стену, я заинтересовался и решил посмотреть, что же из себя представляет эта платформа для общения с пользователями — ask.mcdonalds.ru.

Краткий экскурс:

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

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

Обнаружение уязвимости


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



Таким образом мы видим сразу наличие Stored XSS, которую может использовать каждый, кто пожелает, поскольку обнаружить ее очень просто (кстати, ну и кроме этого, на сайте также присутствует пресловутая logout CSRF).

То есть мы вместе с обычными на первый взгляд именем и фамилией также передаем посредством формы что-нибудь наподобие

  

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

Я решил проверить уязвимость снова:



Интересный факт
Если вы вдруг захотите еще и прочитать то самое «положение об обработке и защите персональных данных», то сделать это вы, увы, не сможете, поскольку по адресу mcdonalds.ru/legacy нас ждет 404 и вот это

image

Но в этот раз код не выполнился. Открыв консоль, я увидел следующее:



В данном случае, страница передана по протоколу HTTPS, но также содержит контент, передаваемый по обычному, открытому HTTP (мой скрипт). Тогда соединение считается частично зашифрованным, а сама страница называется страницей со смешанным содержимым.

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

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

Любой файл, хранящийся у вас на гугл диске, может быть доступен непосредственно по прямой ссылке. Получить ее совсем не сложно:
Делаем файл доступным по ссылке и получаем что-то вроде
https://drive.google.com/file/d/AAAAAAAAAAAAAAAAAAA/view?usp=sharing

затем берем значение после drive.google.com/file/d/, в нашем случае «AAAAAAAAAAAAAAAAAAA», и подставляем его как значение параметра id в ссылку вида
https://drive.google.com/uc?export=download&id=

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



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



Последствия наличия такой уязвимости


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

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

И если представить, вариантов использования масса:

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

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

Сообщение компании о наличии уязвимости и итог


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

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

Эксплуатировать уязвимость я не намерен, посему надеюсь, что эта статья также поможет донести информацию до компании, и уязвимость будет исправлена.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334564/


Метки:  

[Из песочницы] Разграничение прав доступа в PostgreSQL

Понедельник, 31 Июля 2017 г. 17:32 + в цитатник


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


Способ 1. Встроенные механизмы аутентификации


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


Плюсы такого подхода: его простота и прозрачность. По логам СУБД легко увидеть, какие запросы выполняют пользователи, несколько прав можно объединять в роли и раздавать их пользователям прямо "из коробки". Основной минус такого подхода — отсутствие контроля доступа на уровне строк. Да, в 9.5 появилась row-level security, но этот механизм работает не так быстро, как хотелось бы, особенно для JOIN.


К встроенным механизмам аутентификации также относятся LDAP, PAM, GSSAPI и прочие.


Способ 2. Проверка на уровне приложения


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


  1. Отсутствует контроль доступа на уровне строк, либо он становится очень сложным.
  2. В случае компрометации пароля пользователя СУБД злоумышленник получает полный доступ ко всем данным, причём он сможет не только читать их, но и изменять.
  3. Приложение становится единственной звеном, контролирующим доступ и если вы, допустим, захотите реализовать ещё какой-нибудь сервис, работающий с базой, вам придётся писать весь код, выполняющий проверки, заново.

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


Способ 3. Введение сессии на уровне СУБД


Об этом способе я сегодня и хочу рассказать поподробнее. Суть его проста: в базе данных создаётся процедура авторизации, которая проверяет логин и пароль пользователя и в случае успеха устанавливает значение некоторой сессионной переменной, которая была бы доступна на чтение до конца текущей сессии. Для хранения значения переменной будем использовать глобальный массив GD, доступный процедурам на языке Pl/Python:


create or replace
function set_current_user_id(user_id integer) as $$
  GD['user_id'] = user_id
$$ language plpythonu;

Сама же процедура авторизации будет выглядеть следующим образом:


create or replace
function login(user_ text, password_ text) returns integer as $$
declare
  vuser_id integer; vis_admin boolean;
begin
  select id, is_admin
    into vuser_id, is_admin
    from users where login = login_ and password = password_;

  if found then
    perform set_current_user_id(vuser_id);
    /* код функции set_is_admin() аналогичен
       коду функции set_current_user_id() */
    perform set_is_admin(vis_admin);
  else
    raise exception 'Invalid login or password';
  end if;

  return vuser_id;
end;
$$ language plpgsql security definer;

После этого осталось реализовать функцию, которая будет возвращать ID залогиненного пользователя:


create or replace
function get_current_user_id() returns integer as $$
  return GD.get('user_id')
$$ language plpythonu stable;

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


create or replace
function delete_branch(branch_id_ integer) returns void as $$
begin
  if not current_user_is_admin() then
    raise exception 'Access denied: this operation needs admin privileges';
  end if;
  ...
end;
$$ language plpgsql;

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


create or replace
function get_accounts() returns table (account_number text) as $$
begin
  return query
  select a.account_number
    from accounts a
    join users u on u.branch_id = a.branch_id
   where u.id = get_current_user_id();
end;
$$ language plpgsql;

В чём плюсы и минусы такого подхода? Плюсы:


  1. Удобство использования, гибкость, расширяемость.
  2. Обеспечение разграничения доступа на уровне строк практически без ущерба для производительности СУБД.
  3. Вся логика сосредоточена в СУБД, таким образом можно предоставлять доступ к базе данных нескольким приложениям, в которых придётся реализовать лишь механизм авторизации.
  4. Кроме информации о самом пользователе, можно оперативно получать любые метаданные, связанные с ним — например, является ли текущий пользоватль администратором, его имя для отображения в каком-нибудь личном кабинете, группы, к которым он принадлежит, и так далее.

Несмотря на это, есть также и минусы:


  1. Всю логику работы с данными необходимо оборачивать в хранимые процедуры (на самом деле, для меня это плюс).
  2. Необходимость авторизации пользователя в начале каждой сессии, а если код обёрнут в транзакции, то в начале каждой транзакции. Это может быть некритично для так называемых "толстых клиентов", но для веб приложений уже становится актуальным. В этом случае проблема решается оборачиванием драйвера, который предоставляет доступ к СУБД кастомным кодом таким образом, чтобы авторизация выполнялась перед выполнением каждого запроса. Звучит не очень красиво, но на самом деле всё не так страшно. Я в своих проектах использовал Flask и модуль flask_login, который сильно упрощает эту задачу.

Резюме


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

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

https://habrahabr.ru/post/334558/


Метки:  

Машинное обучение для страховой компании: Оптимизация модели

Понедельник, 31 Июля 2017 г. 17:29 + в цитатник

Четверть миллиона за «жука»: Microsoft начинает активную борьбу с багами

Понедельник, 31 Июля 2017 г. 17:17 + в цитатник
Вторая половина июля оказалась щедрой на новости от Microsoft о борьбе с багами. 21 июля стало известно о запуске облачной платформы для поиска уязвимостей, а 26 июля представители компании объявили о новом этапе поощрительной программы — корпорация готова выплачивать до $250 000 за найденные баги.

/ Flickr / Mike Mozart / CC

Искусственный интеллект на страже безопасности


Облачное решение по поиску уязвимостей было запущено на базе сервиса Microsoft Security Risk Detection. Ранее известный как Project Springfield, он создан для обнаружения критических ошибок в безопасности программного обеспечения методом фаззинга. Microsoft использовала эту технологию для поиска критических ошибок в Windows и Office.

В 2016 году, помимо Springfield, у проекта было и неофициальное название — «баг-детектор на миллион». Сообщалось, что он помог Microsoft выявить серьезные уязвимости, которые оценивались приблизительно в $1 млн. Сейчас приложение готовится к массовому релизу на платформе Azure.

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

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

По словам исследователя из Microsoft Дэвида Молнара (David Molnar), этот инструмент подходит для компаний, которые сами создают программное обеспечение, модифицируют готовое ПО или предлагают лицензии с открытым исходным кодом.

Тема AI-платформ для выявления багов активно «всплывает» в дискуссиях о перспективных технологиях. Например, в 2016 году Майк Уокер (Mike Walker), научный сотрудник Управления перспективных исследовательских проектов Министерства обороны США, представил свое видение будущего компьютерной безопасности. Результаты конкурса Grand Cyber Challenge DARPA, по его мнению, доказали, что искусственный интеллект способен находить и исправлять ошибки самостоятельно.

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

Четверть миллиона за внимательность


Менее чем через неделю после показа своего облачного сервиса Microsoft анонсировали расширение программы поощрения за поиск уязвимостей (bug bounty) в продуктах для Windows. Сумма вознаграждений варьируется от $500 до $250 000 за баги в различных ПО.

Корпорация предлагает сосредоточиться на уязвимых местах таких продуктов, как Windows Insider Preview, Hyper-V, Windows Defender Application Guard и Microsoft Edge.

/ Pixabay / VISHNU_KV / CC

Претендовать на максимальную награду в $250 000 могут обнаружившие уязвимости в системе аппаратной визуализации Hyper-V. О ряде серьезных уязвимостей Hyper-V в этом году предупреждала «Лаборатория Касперского».

Поощрительные программы пользуются популярностью у таких технологических компаний, как Facebook, Google, Microsoft, Mozilla, Uber, Yahoo, так как они позволяют привлечь к поиску потенциальных уязвимостей большое количество замотивированных наградой участников.

Люди охотно подключаются к выявлению багов, и для кого-то это становится прибыльным занятием. Например, стартап HackerOne, позволяющий любой компании обратиться к услугам хакеров с целью взлома своей системы безопасности, за первые четыре года на рынке заработал более $7 млн. Среди клиентов HackerOne числятся Slack, Twitter, Yahoo и Uber.

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

Говоря о потенциальных потерях. По данным «Лаборатории Касперского» от 2016 года, в среднем один инцидент обходится крупному предприятию в $861 000. Малому же и среднему бизнесу уязвимость в безопасности встает в $86 500 за атаку.

Кроме прямых потерь от киберугроз, уязвимости порой имеют непредсказуемые негативные последствия. Так, американский регулятор оштрафовал в 2016 году 12 финансовых компаний на $14,4 млн за недостатки систем кибербезопасности. А компания Yahoo потеряла в цене $100 млн из-за ставших достоянием общественности инцидентов с хакерскими атаками.

В 2013 году был представлен доклад исследователей Калифорнийского университета, который утверждал, что выплата вознаграждений независимым энтузиастам в области безопасности за поиск багов выгоднее, чем наем сотрудников для выполнения той же работы. В документе были рассмотрены программы поощрения уязвимостей Google и Mozilla для браузеров Chrome и Firefox. В период с 2010 по 2013 год компания Google заплатила за найденные ошибки $580 тыс., а Mozilla — $570 тыс.

Кроме очевидной выгоды, bug bounty помогает находить талантливых сотрудников. Во время брифинга на июльской конференции по кибербезопасности Black Hat USA 2017 в Лас-Вегасе компания Salesforce раскрыла сведения о выплате более $2 млн за выявление багов с 2015 года. Одним из сильнейших участников программы Salesforce стал 16-летний студент из Аргентины. Его активность оценили в компании, и он вместе с семьей переехал в Сан-Франциско, чтобы помогать отделу безопасности Salesforce.

Человеческий или искусственный интеллект?


Как уже было сказано выше, прежде чем стать массовым продуктом Security Risk Detection являлся инструментом для внутреннего пользования Microsoft. Тем не менее наряду с ним корпорация прибегала к краудсорсингу. Значит ли это, что искусственный интеллект не способен исполнять функцию баг-трекера?

Сам Дэвид Молнар из Microsoft отводит автоматической службе роль дополнительного помощника разработчикам.

Дэвид Брамли (David Brumley), соучредитель ForAllSecure, разработчика ПО для обеспечения безопасности, уверяет, что компьютеру потребуется время, дабы заменить человека. Причина заключается в отсутствии у искусственного интеллекта творческого подхода, который свойственен киберпреступникам. Программа способна действовать по шаблону, даже обучаясь в процессе, но для выявления всех возможных уязвимостей ей следует на 100% учитывать человеческий фактор.

Похожую мысль высказывает и Саймон Кросби (Simon Crosby), технический директор компании Bromium, отвечающей за решения в области безопасности. Он поделился с Hewlett Packard Enterprise следующей мыслью: «Что AI действительно может делать, так это просматривать огромные объемы данных, которые мы получаем из всех видов систем, и выявлять аномальное поведение [...] Поэтому искусственный интеллект, как правило, приходится кстати в вопросах аналитики».

Популярная в последнее время тема роботизации рабочих мест вынуждает думать, что искусственный интеллект эффективнее человеческого, в том числе и в вопросе кибер-безопасности. Так, согласно недавнему опросу тренинговой компании Udemy, 43% американских рабочих обеспокоены тем, что потеряют работу из-за развития AI. Но по прогнозам, к 2021 году во всем мире будут наняты 3,5 млн профессионалов в области безопасности. Так что от человеческого труда в этой сфере корпорации пока отказаться не готовы.

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

P.S. Еще несколько материалов из нашего блога по теме ИБ:

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

https://habrahabr.ru/post/334552/


Метки:  

[Перевод] Взлом игры Clocktower — The First Fear

Понедельник, 31 Июля 2017 г. 17:15 + в цитатник
Давайте возьмём отличную японскую игру в жанре survival horror, разберёмся, как она работает, переведём её на английский и сделаем с ней ещё кое-что.

Введение


ClockTower (известная в Японии как Clocktower — The First Fear) — это игра, изначально выпущенная Human Entertainment для Super Nintendo.



Это одна из игр жанра «point and click adventure», но она также стала одним из основателей жанра survival horror. Сюжет заключается в том, что четыре сироты после закрытия приюта попали в особняк, в котором один за другим начали исчезать. Игрок управляет одной из сирот, Дженнифер, и пытается найти выход, своих друзей и выяснить, что же происходит в особняке.

Атмосфера игры сурова, события в доме происходят случайно, без какой-либо видимой причины (проще говоря, дом заколдован), иногда что-то может убить вас просто так, психопат с садовыми ножницами преследует вас по всему дому (Немезис из Resident Evil 3 был не первым таким персонажем):



В игре часто возникают опасные ситуации, требующие быстро думать, единственное спасение — выживать, убегать и прятаться, но также важно сохранение рассудка Дженнифер, иначе она может упасть и умереть от сердечного приступа (в стиле игры Illbleed).

Игра была выпущена в 1994 году на SNES, повторно выпущена с расширенным контентом на PSX в 1997 году, выпущена на консоли Wonderswan (но никого не волнует Wonderswan), и, наконец, вышла на PC (а потом перевыпущена для PC в 1999 году).

Да, для ROM под SNES группой Aeon Genesis выпущен английский перевод (кажется, это были они), но версия для SNES хуже версии для PC, у которой был гораздо лучший звук, есть вступительный FMV-ролик и т.д. Возможно, единственной версией с бОльшим количеством контента была версия на PSX, но о ней мы поговорим позже.

Для начала давайте «отучим» игру от CD!

Часть 1 — Исследование


У нас есть папка с игрой, которая выглядит так:



У нас есть папка BG, внутри которой находится что-то похожее на фоны комнат в разрезанном формате с сопровождающими файлами .MAP, который, как я предполагаю, размещает разные фрагменты на экране:



У нас есть папка DATA, содержащая файлы PTN и PYX… пока не знаю, что это такое… возможно, они связывают экраны уровней с логикой, или что-то подобное.

Также есть папка FACE, в которой содержатся bmp с лицами людей, используемыми в диалогах:



Ещё есть папка с предметами, которые может использовать Дженнифер:



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

В папке SCE находятся два файла: CT.ADO и CT.ADT. Мы рассмотрим их — это логические скрипты действий, происходящие на протяжении всей игры, написанные на собственном бинарном скриптовом языке… и на них мы потратим больше всего времени.

В папке SNDDATA лежат файлы MIDI и WAV музыки и звуковых эффектов.

Папка VISUAL содержит все рендеры высокого разрешения, опции меню и т.д. в формате BMP. Также здесь хранится вступительное видео.



Наконец, у нас есть файл DATA.BIN, который… не знаю пока, что делает. Кроме того, есть исполняемый файл игры ct32.exe.

Запустив игру без CD, получаем следующее сообщение:



Запустим игру в отладчике, чтобы найти эту строку. Выясняется, что она просто сообщает, что игра не знает, где находятся данные игры (т.е. не вставлен CD).

Часть 2 — Создаём NoCD


Посмотрев в IDA, мы видим, что используется RegQueryValueEx, то есть считывается значение реестра. Что же игра оттуда получает?



Ну… полагаю, вот и ответ.

ХОТЯ СТОП.

Давайте проверим, что её вызывает, возможно, мы сможем жёстко задать это в коде:



Похоже, что дополнительный труд оказался ненапрасным! Игра ищет аргумент NOREG. Если она не находит его, то проверяет значение реестра, пропускает проверку и запускает игру, предполагая, что находится в родительском каталоге. Здесь нам поможет простой патч, который заставит игру думать, что она всегда запускается с аргументом NOREG. Для этого заменим jz на 0xEB (или безусловный JMP).

Запускаем и получаем:



ДА!

Часть 3 — Ищем внутриигровой текст


Подумаем немного — можно ли ожидать, что весь текст находится в исполняемом файле? Ну, это возможно, во многих играх так и бывает. В двоичных файлах есть даже несколько статичных строк (например, найденная нами ошибка об отсутствии CD), но у меня есть ощущение, что тексты спрятаны где-то в другом месте (может, в файлах DATA?). Давайте посмотрим:



Японский текст, использующий формат SHIFT-JIS (очень популярный), обычно начинается с управляющего символа в диапазоне 0x80-0x8F… Неплохо бы начать с поиска чего-то подобного в исполняемом файле.

Затем мы скопируем эти строки в новый файл и откроем в notepad++, а потом вставим в Google Translate:



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



Хм, понятно — в файлах PTN и PYX нет никаких текстов. Давайте проверим папку SCE.



Похоже, что в файле CT.ADT находятся четырёхбайтные смещения (они до конца файла выполняют счёт вверх от 0x100, по 4 байта за раз).

Зато в файле CT.ADO…





Мы что-то нашли… здесь не только строки ASCII, похожие на пути к файлам, но и текстовые строки в формате SHIFT-JIS. Данные в этом файле выглядят странно… давайте посмотрим, сможем ли мы разобраться в них лучше. Нам придётся, если мы хотим их парсить.

Часть 4 — Углубляемся в ADO/ADT


Мы уже немного разобрались в том, как работает ADT, так что давайте посмотрим, как выглядит ADO:



В самом верху начинается магия (ADC Object File), потому куча 0xFF для смещения на 256, потом, похоже, начинаются данные. Настало время подумать, как настоящий учёный-компьютерщик!

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

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

За 39FF следует нулевое двухбайтное значение (чаще всего, но иногда это 0x0100, поэтому я думаю, что это значение в WORD), затем строка ASCII, завершающаяся нулевым значением, и отступ. На самом деле при каждой загрузке BMP для FACE, перед ней стоит значение 0xFF39!

Давайте проверим исполняемый файл:



Здорово! У нас есть не только это, но и другие значения. Давайте проверим несколько строк Shift-JIS, чтобы проверить, сможем ли найти закономерность:



Отлично! Все строки начинаются с 0xFF33, имеют два 16-битных значения (0x0F00), за которыми идёт строка в Shift-JIS.

Примечание: можно заметить, что SHIFT-JIS НЕ завершается нулевым значением, это невозомжно. Некоторые программы могут обрабатывать и однобайтовые и многобайтовые символьные значения, но в старых программах это было серьёзной проблемой. В результате, как можно заметить, за всеми разрывами строк (0x0a) следуют 0x00. На самом деле за ВСЕМИ символами ASCII здесь идёт 0x00 (и числа или английские буквы). Таким образом рендеринг текста может поддерживать многобайтовую интерпретацию символов ASCII (0-127) и не перепутать при считывании байт данных с управляющим байтом, и наоборот.

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

Итак, как мы знаем, файл ADO забит скриптовыми «опкодами» и следующими за ними данными. Можно теоретически предположить, что игра считывает их и на основании предшествующего опкода знает, какие данные нужно ожидать. Теперь мы можем посмотреть на конструкцию switch в исполняемом файле, которую мы нашли выше (со всеми case), чтобы выделить все опкоды, используемые в игре (для более полного понимания формата).

Мы заметим значения (0xFF20, 0xFF87) и будем искать их в файле ADO, определяя, имеют ли они то же количество байт данных перед следующим опкодом. Попробуем выяснить, являются ли они двухбайтными значениями, строками и так далее.



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



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



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

На самом деле, после первого JMP (0xFF22) есть двухбайтовое значение 0x17.

Если наблюдать за ним в игре и установить в IDA в качестве наблюдаемой переменной ADO_offset, то можно увидеть, что игра переходит от этого значения к 0x1B32. Как она узнаёт, что нужно это сделать? Это не множитель… наверно:



Ага!

0x17 * 4 = 0x5C, то есть ADT — это таблица переходов для разных сцен. Вы заметите, что функция CALL (0xFF23) работает похожим образом, но через какое-то время возвращается к этому смещению… Первые несколько смещений ADT указывают на 0xFF00. Похоже, это очень важно в игре, переходы на самом деле пропускают их (они прибавляют +2 к смещению после перехода). Это что-то вроде опкода RETN? Думаю, что да.

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



Сделав дамп памяти, можно заметить, что файл объектов ADC в памяти (CT.ADO) имеет значение 0x8000 int16, записанное в каждых 0x8000, или 32 КБ. Кроме этого ADO никак не изменён. Можно также заметить в исполняемом файле, что функция парсит значения и пропускает два байта, если видит это значение (как NOP).



Поскольку игра разделяет данные на фрагменты по 32 КБ (скорее всего, чтобы более сегментированно обращаться к памяти; мы часто встречались с двухбайтными значениями — это важно), то для ADT должна быть какая-то трансляция адресов (потому что ADT использует для обращения к смещению 4 байта).



Здесь довольно много математики, которая, если вы наблюдали в отладчике, будет напоминать что-то такое:



Сначала я не нашёл функцию и дошёл до конца файла ADT, предполагая, что есть указатель на конец файла ADO (последний RETN имеет смещение 0x253F4). В списке ADT он указан как 0x453F4. Поискав разные другие адреса, я заметил, что трансляция берёт два значимых байта, делит их пополам и вставляет обратно в конец.

Неплохо, теперь мы можем генерировать файлы ADT (обратной операцией: умножением самого значимого в зависимости от того, сколько интервалов по 0x8000 мы прошли). Также у нас есть общая схема опкодов и мы знаем, где находятся строки. Прежде чем заняться дизассемблированием, давайте посмотрим на награду и приложим все усилия (сначала перевод).

Часть 4: настала очередь CTTDTI


Мы знаем, в каком формате хранятся строки и как считать их из файла ADO. Разумеется, вставка их обратно потребует изменения смещений ADT, потому что размеры строк могут быть гораздо больше или меньше. Для начала рассмотрим вырезание строк ADO в текстовый файл, во что-то очень легко редактируемое, что можно будет потом считать другой программой. Создадим формат, в котором мы сможем легко вставлять строки в новый файл ADO и удобно изменять смещения. Что нам для этого понадобится?

Итак, нам важно смещение, с которого начинается строка, размер исходной строки в байтах, затем сама строка, что-то вроде:

0xE92 25 blahblahblah

Пишем cttd.py:

'''
 CTD - Clocktower Text Dumper by rFx
'''

import os,sys,struct,binascii

f = open("CT_J.ADO","rb")
data = f.read()
f.close()
g = open("ct_txt.txt","wb")
for i in range(0,len(data)-1):
	if(data[i] == '\x33' and data[i+1] == '\xff'):
		#Нам нужно пропускать 6 из-за опкодов и значений, изменения которых нам не важны.
		i+=6
		str_offset = i
		str_end = data[i:].index('\xff') -1
		newstr = data[i:i+str_end]
		strlen = len(newstr)
		newstr = newstr.replace("\x0a\x00","[NEWLINE]")
		#Игра ставит нули после каждого символа ASCII, нам нужно удалить их.
		newstr = newstr.replace("\x00","")
		g.write("%#x\t%d\t" % (str_offset,strlen))
		g.write(newstr)
		g.write("\n")

g.close()

Я переименовал CT.ADO в CT_J.ADO, чтобы потом сгенерировать новый файл.

Эта программа считывает файл ADO, находит 0xFF33, пропускает 6 байтов (чтобы обойти опкод и два двухбайтных значения), а затем записывает в новый файл начальное смещение строки, длину строки и саму строку в формат с разделителями табуляцией, завершающийся новой строкой.

Вы заметите, что я заменяю все значения 0x0a (новая строка) на [NEWLINE]. Я делаю так потому, что хочу, чтобы вся строка обрабатывалась в одной строке и я мог определять новые строки там, где хочу, не изменяя формат текстового файла.

Ради интереса давайте сделаем что-нибудь глупое: мы спарсим этот текст с помощью translator. Это модуль Python, загружающий данные в Google Translate, автоматически распознающий язык, переводящий и возвращающий текст на нужном языке:

cttt.py:

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
Clocktower Auto Translator by rFx
'''
import os,sys,binascii,struct
from translate import Translator
translator = Translator(to_lang="en") #Set to English by Default



f = open("ct_txt.txt","rb")
g = open("ct_txt_proc2.txt","wb")
proc_str = []
for line in f.readlines():
	proc_str.append(line.rstrip())

for x in range(0,len(proc_str)):
	line = proc_str[x]
	o,l,instr = line.split("\t")

	ts = translator.translate(instr.decode("SHIFT-JIS").encode("UTF-8"))

	ts = ts.encode("SHIFT-JIS","replace")
	proc_str[x] = "%s\t%s\t%s" % (o,l,ts)
	g.write(proc_str[x]+"\n")


#for pc in proc_str:
#	g.write(pc)
	
g.close()

Теперь давайте попробуем пару строк с инъектором — последняя программа в этом пакете парсит текстовый файл, добавляет отбивку нулями ко всем символам ASCII в строках, считывает строки в словарь, чтобы мы знали, какие смещения задействуются. Кроме того, он воссоздаёт ADO с нуля (он считывает ADT, загружает все «сцены» в массив с их смещениями, копирует все данные между строками и после них), а затем повторно генерирует ADT на основании размеров «сцен» ADO:

ctti.py:

'''
Clocktower Text Injector by rFx
'''
import os,sys,struct,binascii

def is_ascii(s):
    return all(ord(c) < 128 for c in s)

def get_real_offset(offset):
	#Получаем верхнее значение
	high_val = offset & 0xFFFF0000
	high_val /= 2
	low_val = offset & 0xFFFF
	return high_val+low_val
def get_fake_offset(offset):
	#Получаем верхнее значение
	mult = int(offset / 0x8000)
	shft_val = 0x8000 * mult

	low_val = offset & 0xFFFF

	return offset + shft_val


f = open("CT_J.ADO","rb")
data = f.read()
f.close()

offset_vals = []
adt_list = []
newdata = ""
f = open("ct_txt_proc.txt","rb")
lines = f.readlines()
o,l,s = lines[0].split("\t")
first_offset = int(o,16)
o,l,s = lines[0].split("\t")
last_offset_strend = int(o,16) + int(l)
newdata = data[:first_offset]

for i in range(0,len(lines)):
	line = lines[i]
	offset, osl, instr = line.split("\t")
	offset = int(offset,16)

	instr = instr.rstrip('\n')

	instr = instr.replace("[NEWLINE]","\x0a")
	#Исправляем символы ASCII.
	instr = instr.decode("SHIFT-JIS")
	newstr = ""
	for char in instr:
		if(is_ascii(char)):
			newstr+=char+'\x00'
		else:
			newstr+=char
	instr = newstr
	instr = instr.encode("SHIFT-JIS")
	newstrlen = len(instr)
	osl = int(osl)
	strldiff = newstrlen - osl

	#Заменяем данные
	if(i < len(lines)-1):
		nextline = lines[i+1]
		nextoffset,nsl,nstr = nextline.split("\t")
		offset_vals.append({"offset":offset,"val":strldiff})
		nextoffset = int(nextoffset,16)

		newdata += instr+data[offset+osl:nextoffset]
	else:
		offset_vals.append({"offset":offset,"val":strldiff})
		newdata += instr + data[offset+osl:]



#Конец последней строки до EOF

f.close()

#Записываем новый файл ADO.
g = open("CT.ADO","wb")
g.write(newdata)
g.close()

#Исправляем файл ADT.
f = open("CT_J.ADT","rb")
datat = f.read()
f.close()
g = open("CT.ADT","wb")

for i in range(0,len(datat),4):
	cur_offset = get_real_offset(struct.unpack(" offset["offset"]):
			final_adj += offset["val"]
	g.write(struct.pack("code>

Проверим, как он работает:



Отлично!

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



Всё переведено и готово к работе! Давайте запустим:



Чёрт возьми!

Что-то не так, давайте забросим файл в IDA и посмотрим, что происходит:





Похоже, что мы считываем файл ADO в память, но он пытается использовать указатель и не может, потому что в этом месте ничего нет.



Понаблюдаем за struct a1 и за выполняемыми ею malloc, наверно, проблема в этом. Покопавшись подольше, мы выясним, что эти указатели создаются здесь:



То есть игра (на основании опкода cmp5) создаёт указатели только для фрагментов ADO 5 * 0x8000, но считывает данные ADO до EOF (это определённо ошибка). В результате мы можем загрузить файл ADO размером не более 0x28000. Остановит ли это нас? Конечно нет! Давайте подробнее изучим структуру SCE в памяти…

Мы можем изменить все случаи загрузки указателей ADO с 5 на 6, чтобы добавить ещё один указать, но что будет после этого последнего указателя? Конечно же, начнутся смещения ADT



Мы видим, что смещение ADT 0x00 находится по адресу struct_head + 0x2A и переходит на 0x7D0… серьёзно? Указетели на 0x7D0??? Постойте, это похоже на 0x8000.

В результате у нас есть только 0x4800 байт для файла ADT. Можно сказать, что снизив начальный индекс ADT до, скажем, 0x2E, мы получим в четыре раза больше байт для записи ещё одного указателя ADO и у нас ещё останется куча свободного места в конце!

Находим ссылки struct на 0x2A и меняем их тоже на 0x2E:



Да! Люблю объектно-ориентированный реверс-инжиниринг.

Отлично, теперь игра полностью переведена. Что дальше?

Нам надо внести двоичные изменения в CT32.exe с помощью шестнадцатеричного редактора:

 529B:	74	EB
 BC7B:	2A	2E
 BC8D:	D0	CC
 BD35:	2A	2E
 BD62:	2A	2E
 D4DA:	2A	2E
 D4FC:	2A	2E
 DA58:	2A	2E
 DA79:	2A	2E
103DA:	2A	2E
10407:	2A	2E
104F8:	2A	2E
105BB:	2A	2E
105E8:	2A	2E
10703:	2A	2E
10730:	2A	2E
115FA:	2A	2E
116B2:	05	06
116E8:	05	06
11720:	2A	2E
11729:	D0	CC
1195D:	05	06
1C50F:	20	00

Работа на будущее — часть 5 — SCEDASM — дизассемблер SCE


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

Что-то вроде такого:

'''
Clocktower ADC Object File Disassembler by rFx
'''
import os,sys,binascii,struct

ADO_FILENAME = "CT_J.ADO"
ADT_FILENAME = "CT_J.ADT"

ADO_OP = {
0xFF00:"RETN", #Scene Prologue - 0 bytes of data. - Also an END value... the game looks to denote endings.
0xFF01:"UNK_01", # varying length data
0xFF02:"UNK_02", # 3 bytes of data
0xFF03:"UNK_03", # 3 bytes of data
0xFF04:"UNK_04", # 3 bytes of data
0xFF05:"UNK_05", # 3 bytes of data
0xFF06:"UNK_06", # 3 bytes of data
0xFF07:"UNK_07", # 3 bytes of data
0xFF0A:"UNK_0A", # 4 bytes of data. Looks like an offset to another link in the list?
0xFF0C:"UNK_0C", # 4 bytes of data
0xFF0D:"UNK_0D", # 4 bytes of data
0xFF10:"UNK_10", # 4 bytes of data
0xFF11:"UNK_11", # 4 bytes of data
0xFF12:"UNK_12", # 4 bytes of data
0xFF13:"UNK_13", # 4 bytes of data
0xFF14:"UNK_14", # 4 bytes of data
0xFF15:"UNK_15", # 4 bytes of data
0xFF16:"UNK_16", # 4 bytes of data
0xFF1F:"UNK_1F", # 0 bytes of data
0xFF20:"ALL", # 0 bytes of data. Only at the end of the ADO (twice)
#All opcodes above this are like... prologue opcodes (basically in some other list)
0xFF21:"ALLEND",  # 2 bytes of data
0xFF22:"JMP",  # 2 bytes of data - I think it uses the value for the int offset in adt as destination +adds 2
0xFF23:"CALL",  # 6 bytes of data
0xFF24:"EVDEF", # Not used in the game
0xFF25:"!!!!!!", #Not used in the game
0xFF26:"!!!!!!", #Not used in the game
0xFF27:"!!!!!!", #Not used in the game
0xFF28:"!!!!!!", #0 bytes of data.
0xFF29:"END_IF", # 4 bytes of data
0xFF2A:"WHILE",  # 4 bytes of data
0xFF2B:"NOP",    # 0 bytes of data
0xFF2C:"BREAK", # Not used in the game
0xFF2D:"ENDIF",  # 2 bytes of data
0xFF2E:"ENDWHILE",  # 2 bytes of data
0xFF2F:"ELSE",   # 2 bytes of data
0xFF30:"MSGINIT",   # 10 bytes of data
0xFF31:"MSGTYPE",   # Not used in the game
0xFF32:"MSGATTR",   # 16 bytes of data
0xFF33:"MSGOUT",   # Varying length, our in-game text uses this. :)
0xFF34:"SETMARK", #Varying length
0xFF35:"SETWAIT",  #Not used in the game
0xFF36:"MSGWAIT", #0 bytes of data
0xFF37:"EVSTART", #4 bytes of data
0xFF38:"BGFILEDISP", #Not used in the game.
0xFF39:"BGLOAD", #Varying length, normally a path to a BMP file is passed in.
0xFF3A:"PALLOAD", #Varying length. Also takes BMP files.
0xFF3B:"BGMREQ", #Varying length - loads a MIDI file into memory.
0xFF3C:"SPRCLR", #2 bytes of data.
0xFF3D:"ABSOBJANIM", #Not used in the game
0xFF3E:"OBJANIM", #Not used in the game.
0xFF3F:"ALLSPRCLR", #0 bytes of data
0xFF40:"MSGCLR", #0 bytes 0f data
0xFF41:"SCREENCLR", #0 bytes of data
0xFF42:"SCREENON", #0 bytes of data
0xFF43:"SCREENOFF", #0 bytes of data
0xFF44:"SCREENIN", # Not used in the game.
0xFF45:"SCREENOUT", # Not used in the game.
0xFF46:"BGDISP", # Always 12 bytes of data.
0xFF47:"BGANIM", #14 bytes of data.
0xFF48:"BGSCROLL",#10 bytes of data.
0xFF49:"PALSET", #10 bytes of data.
0xFF4A:"BGWAIT", #0 bytes of data.
0xFF4B:"WAIT", #4 bytes of data.
0xFF4C:"BWAIT", #Not used in the game.
0xFF4D:"BOXFILL", #14 bytes of data.
0xFF4E:"BGCLR", # Not used in the game.
0xFF4F:"SETBKCOL", #6 bytes of data.
0xFF50:"MSGCOL", #Not used in the game.
0xFF51:"MSGSPD", #2 bytes of data.
0xFF52:"MAPINIT", #12 bytes of data.
0xFF53:"MAPLOAD", #Two Paths... Sometimes NULL NULL - Loads the background blit bmp and the map file to load it.
0xFF54:"MAPDISP", #Not used in the game.
0xFF55:"SPRENT", #16 bytes of data.
0xFF56:"SETPROC", #2 bytes of data.
0xFF57:"SCEINIT", #0 bytes of data.
0xFF58:"USERCTL", #2 bytes of data.
0xFF59:"MAPATTR", #2 bytes of data.
0xFF5A:"MAPPOS", #6 bytes of data.
0xFF5B:"SPRPOS", #8 bytes of data.
0xFF5C:"SPRANIM", #8 bytes of data.
0xFF5D:"SPRDIR", #10 bytes of data.
0xFF5E:"GAMEINIT", #0 bytes of data.
0xFF5F:"CONTINIT", #0 bytes of data.
0xFF60:"SCEEND", #0 bytes of data.
0xFF61:"MAPSCROLL", #6 bytes of data.
0xFF62:"SPRLMT", #6 bytes of data.
0xFF63:"SPRWALKX", #10 bytes of data.
0xFF64:"ALLSPRDISP", #Not used in the game.
0xFF65:"MAPWRT", #Not used in the game.
0xFF66:"SPRWAIT", #2 bytes of data.
0xFF67:"SEREQ", #Varying length - loads a .WAV file.
0xFF68:"SNDSTOP", #0 bytes of data.
0xFF69:"SESTOP", #Varying length - specifies a .WAV to stop or ALL for all sounds.
0xFF6A:"BGMSTOP", #0 bytes of data.
0xFF6B:"DOORNOSET", #0 bytes of data.
0xFF6C:"RAND", #6 bytes of data.
0xFF6D:"BTWAIT", #2 bytes of data
0xFF6E:"FAWAIT", #0 bytes of data
0xFF6F:"SCLBLOCK", #Varying length - no idea.
0xFF70:"EVSTOP", #Not used in the game.
0xFF71:"SEREQPV", #Varying length - .WAV path input, I think this is to play and repeat.
0xFF72:"SEREQSPR", #Varying length - .WAV path input, I think this is like SEREQPV except different somehow.
0xFF73:"SCERESET", #0 bytes of data.
0xFF74:"BGSPRENT", #12 bytes of data.
0xFF75:"BGSPRPOS", #Not used in the game.
0xFF76:"BGSPRSET", #Not used in the game.
0xFF77:"SLANTSET", #8 bytes of data.
0xFF78:"SLANTCLR", #0 bytes of data.
0xFF79:"DUMMY", #Not used in the game.
0xFF7A:"SPCFUNC", #Varying length - usage uncertain.
0xFF7B:"SEPAN", #Varying length - guessing to set the L/R of Stereo SE.
0xFF7C:"SEVOL", #Varying length - guessing toe set the volume level of SE 
0xFF7D:"BGDISPTRN", #14 bytes of data.
0xFF7E:"DEBUG", #Not used in the game.
0xFF7F:"TRACE", #Not used in the game.
0xFF80:"TMWAIT", #4 bytes of data.
0xFF81:"BGSPRANIM", #18 bytes of data.
0xFF82:"ABSSPRENT", #Not used in the game.
0xFF83:"NEXTCOM", #2 bytes of data.
0xFF84:"WORKCLR", #0 bytes of data.
0xFF85:"BGBUFCLR", #4 bytes of data.
0xFF86:"ABSBGSPRENT", #12 bytes of data.
0xFF87:"AVIPLAY", #This one is used only once - to load the intro AVI file.
0xFF88:"AVISTOP", #0 bytes of data.
0xFF89:"SPRMARK", #Only used in PSX Version.
0xFF8A:"BGMATTR",#Only used in PSX Version.
#BIG GAP IN OPCODES... maybe not even in existence.
0xFFA0:"UNK_A0", #12 bytes of data.
0xFFB0:"UNK_B0", #12 bytes of data.
0xFFDF:"UNK_DF", #2 bytes of data.
0xFFE0:"UNK_E0", #0 bytes of data.
0xFFEA:"UNK_EA", #0 bytes of data.
0xFFEF:"UNK_EF" #12 bytes of data.
}

if(__name__=="__main__"):
	print("#Disassembling ADO/ADT...")
	#Read ADO/ADT Data to memory.
	f = open(ADO_FILENAME,"rb")
	ado_data = f.read()
	f.close()
	f = open(ADT_FILENAME,"rb")
	adt_data = f.read()
	f.close()
	scene_count = -1
	#Skip ADO Header
	i = 256
	while i < (len(ado_data) -1):
		cur_val = struct.unpack("

Потом, разумеется, нам нужно будет засунуть всё это обратно в пару ADO/ADT.

Работа на будущее — часть 6 — версия для PSX


В версии игры для PSX тоже используются ADO/ADT. Похоже, мы сможем преобразовать ресурсы и добавить эксклюзивный контент с PSX в версию для PC.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332882/


Метки:  

Характеристики анализатора PVS-Studio на примере EFL Core Libraries, 10-15% ложных срабатываний

Понедельник, 31 Июля 2017 г. 17:12 + в цитатник

EFL Core Libraries and PVS-Studio

После большой статьи про проверку операционной системы Tizen мне было задано много вопросов о проценте ложных срабатываний и о плотности ошибок (сколько ошибок PVS-Studio выявляет на 1000 строк кода). Мои рассуждения о том, что это сильно зависит от анализируемого проекта и настроек анализатора не выглядят как настоящий ответ. Я решил привести конкретные числа, проведя более тщательное исследование одного из проектов, входящих в состав Tizen. Поскольку в обсуждении статьи активное участие принимал Carsten Haitzler, я решил, что будет интересно взять для эксперимента EFL Core Libraries, в разработке которого он участвует. Надеюсь, эта статья поможет Carsten стать поклонником нашего анализатора :).


Предыстория


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

После этого на некоторых сайтах появились соответствующие новостные заметки, а также развернулось несколько дискуссий. Вот некоторые из них:
Хочу высказать признательность Carsten Haitzler, который уделил внимание моей публикации и принял активное участие в её обсуждении.

Поднимались разные темы, к некоторым из которых я дал развернутые комментарии в заметке "Tizen: подводим итоги".

Однако меня преследуют два вечных вопроса:
  • Какой процент ложных срабатываний?
  • Как много ошибок находит PVS-Studio на 1000 строк кода?

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

Поэтому я дам ответ на примере конкретного проекта. Я выбрал EFL Core Libraries. Во-первых, этот проект входит в Tizen. А во-вторых, в его разработке принимает участие Carsten Haitzler, и, думаю, ему будет интересно посмотреть на мои результаты.

Можно было ещё проверить Enlightenment, но у меня не хватило сил на это. Я чувствую, что и без этого статья получится очень длинная.

The Enlightenment Foundation Libraries (EFL) это набор графических библиотек, которые появились в результате разработки Enlightenment, менеджера окон и протокола Wayland.

При проверке EFL Core Libraries использовался свежий код, взятый из репозитория https://git.enlightenment.org/.

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

I will say that we take checking seriously. Coverity reports a bug rate of 0 for Enlightenment upstream (we've fixed all issues Coverity points out or dismissed them as false after taking a good look) and the bug rate for EFL is 0.04 issues per 1k lines of code which is pretty small (finding issues is easy enough i the codebase is large). They are mostly not so big impacting things. Every release we do has our bug rates go down and we tend to go through a bout of «fixing the issues» in the weeks prior to a release.

Что же, давайте посмотрим как продемонстрирует себя анализатор PVS-Studio.

Характеристики


Анализатор PVS-Studio после своей настройки будет выдавать около 10-15% ложных срабатываний при проверке проекта EFL Core Libraries.

Плотность обнаруживаемых на данный момент ошибок в EFL Core Libraries составляет более 0,71 ошибки на 1000 строк кода.

Как проводились расчёты


Проект EFL Core Libraries на момент анализа содержит около 1 616 000 строк кода на языке С и C++. Из них 17,7% — это комментарии. Таким образом, количество строк кода без комментариев — 1 330 000.

После первого запуска я увидел следующее количество сообщений общего назначения (GA):
  • Высокий уровень достоверности: 605
  • Средний уровень достоверности: 3924
  • Низкий уровень достоверности: 1186

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

Почти весь проект написан на C и, как следствие, в нём широко используются макросы. Именно макросы и становятся причиной большинства ложных срабатываний. Я потратил около 40 минут на быстрый просмотр отчёта и составил файл efl_settings.txt.

Файл содержит необходимые настройки. Чтобы их использовать при проверке проекта, необходимо в конфигурационном файле анализатора (например, в PVS-Studio.cfg) указать:
rules-config=/path/to/efl_settings.txt

Анализатор может быть запущен так:
pvs-studio-analyzer analyze ... --cfg /path/to/PVS-Studio.cfg ...

или так
pvs-studio ... --cfg /patn/to/PVS-Studio.cfg ...

в зависимости от используемого способа интеграции.

С помощью настроек я указал анализатору, чтобы он не выдавал некоторые предупреждения для тех строк кода, в которых встречаются названия определённых макросов или выражения. Также я полностью отключил несколько диагностик. Например, я отключил V505. Использовать функцию alloca в циклах нехорошо, но это не является явной ошибкой. Мне не хочется много дискутировать о том, является то или иное предупреждение ложным срабатыванием, и я посчитал, что будет проще что-то отключить.

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

Повторный запуск привёл к следующим результатам:
  • Высокий уровень достоверности: 189
  • Средний уровень достоверности: 1186
  • Низкий уровень достоверности: 1186

Два раза повторяется число 1186. Это не опечатка. Действительно числа так случайно совпали.

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

В сумме я получил 189+1186=1375 сообщений (High+Medium), с которыми и начал работать.

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

Рассчитаем теперь плотность обнаруживаемых ошибок:

950*1000/1330000 = около 0,71 ошибок на 1000 строк кода.

Теперь давайте подсчитаем процент ложных срабатываний:

((1375-950) / 1375) * 100% = 30%

Стоп, стоп, стоп! Но ведь в начале статьи говорилось про 10-15% ложных срабатываний. А здесь 30%.

Сейчас объясню. Итак, просматривая отчёт из 1375 сообщений, я пришел к выводу, что 950 указывают на ошибки. Осталось 425 сообщений.

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

Рассмотрим пример одного из пропущенных мною сообщений.
....
uint64_t callback_mask;
....
static void
_check_event_catcher_add(void *data, const Efl_Event *event)
{
  ....
  Evas_Callback_Type type = EVAS_CALLBACK_LAST;
  ....
  else if ((type = _legacy_evas_callback_type(array[i].desc)) !=
           EVAS_CALLBACK_LAST)
  {
    obj->callback_mask |= (1 << type);
  }
  ....
}

Предупреждение PVS-Studio: V629 Consider inspecting the '1 << type' expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. evas_callbacks.c 709

Рассмотрим внимательнее строчку:
obj->callback_mask |= (1 << type);

Она используется, чтобы записать 1 в нужный бит переменной callback_mask. Обратите внимание, что переменная callback_mask является 64-битной.

Выражение (1 << type) имеет тип int, поэтому с его помощью можно изменять биты только в младшей части переменной callback_mask. Биты [32-63] изменить нельзя.

Чтобы понять, есть здесь ошибка или нет, нужно разобраться, какой диапазон значений может возвращать функция _legacy_evas_callback_type. Может она вернуть значение больше чем 31? Я не знаю и пропускаю это сообщение.

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

Comment by Carsten Haitzler. Above — actually is a bug that's a result of an optimization that setting bits to decide if it should bother trying to map new event types to old ones (we're refactoring huge chunks of our internals around a new object system and so we have to do this to retain compatibility, but like with any refactoring… stuff happens). Yes — it wraps the bitshift and does the extra work of a whole bunch of if's because the same bits in the mask are re-used for now 2 events due to wrap around. As such this doesn't lead to a bug, just slightly less micro optimizations when set as now that bit means «it has an event callback for type A OR B» not just «type A»… the following code actually does the complete check/mapping. It surely was not intended to wrap so this was a catch but the way it's used means it was actually pretty harmless.

Среди оставшихся 425 сообщений найдутся указывающие на ошибки. Я их просто пропустил.

Если речь зайдет о регулярном использовании PVS-Studio, то можно продолжить его настраивать. Как я говорил, я уже потратил только 40 минут на настройку. Но это не значит, что я сделал всё что мог. Можно ещё сократить число ложных срабатываний отключением диагностик для определённых программных конструкций.

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

Найденные ошибки


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

Также читатель сам может познакомиться со всеми предупреждениями, открыв файл отчета: zip-архив с отчётом. Учтите, что я оставил в файле только предупреждения General высокого и среднего уровня достоверности.

Я просматривал этот отчёт в Windows, открыв его с помощью утилиты PVS-Studio Standalone.

В Linux можно использовать утилиту Plog Converter, которая сконвертирует отчет в один из следующих форматов:
  • xml — удобный формат для дополнительной обработки результатов анализа, поддерживается плагином для SonarQube;
  • csv — текстовый формат, предназначенный для представления табличных данных;
  • errorfile — формат вывода gcc и clang;
  • tasklist — формат ошибок, который можно открыть в QtCreator.

Далее для просмотра отчётов можно использовать QtCreator, Vim/gVim, GNU Emacs, LibreOffice Calc. Подробнее это описано в разделе документации "Как запустить PVS-Studio в Linux" (см. «Просмотр и фильтрация отчёта анализатора»).

V501 (1 ошибка)


Диагностика V501 выявила только одну ошибку, зато красивую. Ошибка находится в функции сравнения, что перекликается с темой недавней статьи "Зло живёт в функциях сравнения".
static int
_ephysics_body_evas_stacking_sort_cb(const void *d1,
                                     const void *d2)
{
   const EPhysics_Body_Evas_Stacking *stacking1, *stacking2;

   stacking1 = (const EPhysics_Body_Evas_Stacking *)d1;
   stacking2 = (const EPhysics_Body_Evas_Stacking *)d2;

   if (!stacking1) return 1;
   if (!stacking2) return -1;

   if (stacking1->stacking < stacking2->stacking) return -1;
   if (stacking2->stacking > stacking2->stacking) return 1;

   return 0;
}

Предупреждение PVS-Studio: V501 There are identical sub-expressions 'stacking2->stacking' to the left and to the right of the '>' operator. ephysics_body.cpp 450

Опечатка. Последнее сравнение должно быть таким:
if (stacking1->stacking > stacking2->stacking) return 1;


V512 (8 ошибок)


Для начала взглянем на определение структуры Eina_Array.
typedef struct _Eina_Array Eina_Array;
struct _Eina_Array
{
   int version;
   void **data;
   unsigned int total;
   unsigned int count;
   unsigned int step;
   Eina_Magic __magic;
};

Внимательно рассматривать её не надо. Просто структура с какими-то полями.

Теперь посмотрим на определение структуры Eina_Accessor_Array:
typedef struct _Eina_Accessor_Array Eina_Accessor_Array;
struct _Eina_Accessor_Array
{
   Eina_Accessor accessor;
   const Eina_Array *array;
   Eina_Magic __magic;
};

Обратите внимание, что в структуре Eina_Accessor_Array хранится указатель на структуру Eina_Array. В остальном же эти структуры никак не связаны между собой и имеют разный размер.

Теперь код, который выявил анализатор и который я не понимаю:
static Eina_Accessor *
eina_array_accessor_clone(const Eina_Array *array)
{
   Eina_Accessor_Array *ac;
   EINA_SAFETY_ON_NULL_RETURN_VAL(array, NULL);
   EINA_MAGIC_CHECK_ARRAY(array);
   ac = calloc(1, sizeof (Eina_Accessor_Array));
   if (!ac) return NULL;
   memcpy(ac, array, sizeof(Eina_Accessor_Array));
   return &ac->accessor;
}

Предупреждение PVS-Studio: V512 A call of the 'memcpy' function will lead to the 'array' buffer becoming out of range. eina_array.c 186

Давайте я уберу все лишние детали, чтобы было проще:
.... eina_array_accessor_clone(const Eina_Array *array)
{
   Eina_Accessor_Array *ac = calloc(1, sizeof (Eina_Accessor_Array));
   memcpy(ac, array, sizeof(Eina_Accessor_Array));
}

Выделяется память для объекта типа Eina_Accessor_Array. Далее происходит странная вещь.

В выделенный буфер памяти копируется объект типа Eina_Array.

Шта?


Я не знаю, что должна делать рассмотренная функция, но она делает что-то не то.

Во-первых, при копировании происходит выход за границу источника (структуры Eina_Array).

Во-вторых, вообще такое копирование не имеет смысла. Структуры имеют набор полей совершенно разного типа.

Comment by Carsten Haitzler. Function content correct — Type in param is wrong. It didn't actually matter because the function is assigned to a func ptr that does have the correct type and since it's a generic «parent class» the assignment casts to a generic accessor type thus the compiler didn't complain and this seemed to work.

Рассмотрим следующую ошибку.
static Eina_Bool _convert_etc2_rgb8_to_argb8888(....)
{
   const uint8_t *in = src;
   uint32_t *out = dst;
   int out_step, x, y, k;
   unsigned int bgra[16];
   ....
   for (k = 0; k < 4; k++)
     memcpy(out + x + k * out_step, bgra + k * 16, 16);
   ....
}

Предупреждение PVS-Studio: V512 A call of the 'memcpy' function will lead to overflow of the buffer 'bgra + k * 16'. draw_convert.c 318

Здесь всё просто. Обыкновенный выход за границу буфера.

Массив bgra состоит из 16 элементов типа unsigned int.

Переменная k принимает в цикле значения от 0 до 3.

Обратите внимание на выражение: bgra + k * 16.

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

Впрочем, некоторые сообщения V512 указывают на код, который не содержит настоящей ошибки. Однако я не считаю такие срабатывания анализатора ложными. Код плох и, на мой взгляд, должен быть изменён. Рассмотрим такой случай.
#define MATRIX_XX(m) (m)->xx
typedef struct _Eina_Matrix4 Eina_Matrix4;
struct _Eina_Matrix4
{
   double xx;
   double xy;
   double xz;
   double xw;

   double yx;
   double yy;
   double yz;
   double yw;

   double zx;
   double zy;
   double zz;
   double zw;

   double wx;
   double wy;
   double wz;
   double ww;
};

EAPI void
eina_matrix4_array_set(Eina_Matrix4 *m, const double *v)
{
   memcpy(&MATRIX_XX(m), v, sizeof(double) * 16);
}

Предупреждение PVS-Studio: V512 A call of the 'memcpy' function will lead to overflow of the buffer '& (m)->xx'. eina_matrix.c 1003

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

Comment by Carsten Haitzler. Above and related memcpy's — not a bug: taking advantage of guaranteed mem layout in structs.

Мне он не нравится. Я рекомендую написать как-то так:
struct _Eina_Matrix4
{
  union {
    struct {
      double xx;
      double xy;
      double xz;
      double xw;

      double yx;
      double yy;
      double yz;
      double yw;

      double zx;
      double zy;
      double zz;
      double zw;

      double wx;
      double wy;
      double wz;
      double ww;
    };
    double RawArray[16];
  };
};

EAPI void
void eina_matrix4_array_set(Eina_Matrix4 *m, const double *v)
{
  memcpy(m->RawArray, v, sizeof(double) * 16);
}

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

Первый способ. Добавить комментарий в код:
memcpy(&MATRIX_XX(m), v, sizeof(double) * 16); //-V512

Второй способ. Добавить в файл настроек строчку:
//-V:MATRIX_:512

Третий способ. Использовать базу разметки.

Другие ошибки:
  • V512 A call of the 'memcpy' function will lead to overflow of the buffer '& (m)->xx'. eina_matrix.c 1098
  • V512 A call of the 'memcpy' function will lead to overflow of the buffer '& (m)->xx'. eina_matrix.c 1265
  • V512 A call of the 'memcpy' function will lead to the '& pd->projection.xx' buffer becoming out of range. evas_canvas3d_camera.c 120
  • V512 A call of the 'memcpy' function will lead to the '& pd->projection.xx' buffer becoming out of range. evas_canvas3d_light.c 270
  • V512 A call of the 'memcpy' function will lead to overflow of the buffer 'bgra + k * 16'. draw_convert.c 350


V517 (3 ошибки)


static Eina_Bool
evas_image_load_file_head_bmp(void *loader_data,
                              Evas_Image_Property *prop,
                              int *error)
{
  ....
  if (header.comp == 0) // no compression
  {
    // handled
  }
  else if (header.comp == 3) // bit field
  {
    // handled
  }
  else if (header.comp == 4) // jpeg - only printer drivers
    goto close_file;
  else if (header.comp == 3) // png - only printer drivers
    goto close_file;
  else
    goto close_file;
  ....
}

Предупреждение PVS-Studio: V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 433, 439. evas_image_load_bmp.c 433

Два раза переменная header.comp сравнивается с константой 3.

Другие ошибки:
  • V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 1248, 1408. evas_image_load_bmp.c 1248
  • V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 426, 432. parser.c 426


V519 (1 ошибка)


EOLIAN static Efl_Object *
_efl_net_ssl_context_efl_object_finalize(....)
{
  Efl_Net_Ssl_Ctx_Config cfg;
  ....
  cfg.load_defaults = pd->load_defaults;                // <=
  cfg.certificates = &pd->certificates;
  cfg.private_keys = &pd->private_keys;
  cfg.certificate_revocation_lists =
          &pd->certificate_revocation_lists;
  cfg.certificate_authorities = &pd->certificate_authorities;
  cfg.load_defaults = pd->load_defaults;                // <=
  ....
}

Предупреждение PVS-Studio: V519 The 'cfg.load_defaults' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 304, 309. efl_net_ssl_context.c 309

Повторяется присваивание. Одно присваивание лишнее или забыли скопировать что-то другое.

Comment by Carsten Haitzler. Not a bug. Just an overzealous copy & paste of the line.

Ещё один простой случай:
EAPI Eina_Bool
edje_edit_size_class_add(Evas_Object *obj, const char *name)
{
  Eina_List *l;
  Edje_Size_Class *sc, *s;
  ....
  /* set default values for max */
  s->maxh = -1;
  s->maxh = -1;
  ....
}

Предупреждение PVS-Studio: V519 The 's->maxh' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 8132, 8133. edje_edit.c 8133

Конечно, не все случаи такие очевидные. Тем не менее, я считаю, что предупреждения, перечисленные ниже, скорее всего указывают на ошибки:
  • V519 The 'pdata->seat->object.in' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 1519, 1521. evas_events.c 1521
  • V519 The 'pdata->seat->object.in' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 2597, 2599. evas_events.c 2599
  • V519 The 'b->buffer[r]' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 348, 353. evas_image_load_pmaps.c 353
  • V519 The 'attr_amount' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 13891, 13959. edje_edit.c 13959
  • V519 The 'async_loader_running' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 152, 165. evas_gl_preload.c 165
  • V519 The 'textlen' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 86, 87. elm_code_widget_undo.c 87
  • V519 The 'content' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 313, 315. elm_dayselector.c 315
  • V519 The 'wd->resize_obj' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 3099, 3105. elm_entry.c 3105
  • V519 The 'wd->resize_obj' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 3125, 3131. elm_entry.c 3131
  • V519 The 'idata->values' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 128, 129. elm_view_list.c 129
  • V519 The 'wd->resize_obj' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 2602, 2608. efl_ui_text.c 2608
  • V519 The 'wd->resize_obj' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 2628, 2634. efl_ui_text.c 2634
  • V519 The 'finfo' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 706, 743. evas_image_load_gif.c 743
  • V519 The 'current_program_lookups' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 15819, 15820. edje_cc_handlers.c 15820

Примечание. Carsten Haitzler, комментируя статью, написал, что предупреждения V519, приведённые в списке, являются ложными срабатываниями. Я не согласен с таким подходом. Возможно, код работает верно, но всё равно он заслуживает внимания и правки. Я решил оставить в статье список, чтобы читатели сами могли оценить, являются с их точки зрения повторы в присваивании переменных ложными срабатываниями или нет. Но раз Carsten говорит, что это не ошибки, то я не буду их учитывать при подсчётах.

V522 (563 ошибки)




В проекте EFL беда с наличием проверок: выделилась память или нет. В целом, такие проверки в проекте есть. Пример:
if (!(el = malloc(sizeof(Evas_Stringshare_El) + slen + 1)))
  return NULL;

Более того, они есть даже там, где не надо (см. ниже про предупреждение V668).

Но в огромном количестве мест никакой проверки нет. Рассмотрим для примера парочку сообщений анализатора.

Comment by Carsten Haitzler. OK so this is a general acceptance that at least on Linux which was always our primary focus and for a long time was our only target, returns from malloc/calloc/realloc can't be trusted especially for small amounts. Linux overcommits memory by default. That means you get new memory but the kernel has not actually assigned real physical memory pages to it yet. Only virtual space. Not until you touch it. If the kernel cannot service this request your program crashes anyway trying to access memory in what looks like a valid pointer. So all in all the value of checking returns of allocs that are small at least on Linux is low. Sometimes we do it… sometimes not. But the returns cannot be trusted in general UNLESS its for very large amounts of memory and your alloc is never going to be serviced — e.g. your alloc cannot fit in virtual address space at all (happens sometimes on 32bit). Yes overcommit can be tuned but it comes at a cost that most people never want to pay or no one even knows they can tune. Secondly, fi an alloc fails for a small chunk of memory — e.g. a linked list node… realistically if NULL is returned… crashing is about as good as anything you can do. Your memory is so low that you can crash, call abort() like glib does with g_malloc because if you can't allocate 20-40 bytes… your system is going to fall over anyway as you have no working memory left anyway. I'm not talking about tiny embedded systems here, but large machines with virtual memory and a few megabytes of memory etc. which has been our target. I can see why PVS-Studio doesn't like this. Strictly it is actually correct, but in reality code spent on handling this stuff is kind of a waste of code given the reality of the situation. I'll get more into that later.
static Eina_Debug_Session *
_session_create(int fd)
{
   Eina_Debug_Session *session = calloc(1, sizeof(*session));
   session->dispatch_cb = eina_debug_dispatch;
   session->fd = fd;
   // start the monitor thread
   _thread_start(session);
   return session;
}

Comment by Carsten Haitzler. This is brand new code that arrived 2 months ago and still is being built out and tested and not ready for prime time. It's part of our live debugging infra where any app using EFL can be controlled by a debugger daemon (if it is run) and controlled (inspect all objects in memory and the object tree and their state with introspection live as it runs), collect execution timeline logs (how much time is spent in what function call tree where while rendering in which thread — what threads are using what cpu time at which slots down to the ms and below level, correlated with function calls, state of animation system and when wakeup events happen and the device timestamp that triggered the wakeup, and so on… so given that scenario… if you can't calloc a tiny session struct while debugging a crash accessing the first page of memory is pretty much about as good as anything… as above on memory and aborts etc.

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

Предупреждение PVS-Studio: V522 There might be dereferencing of a potential null pointer 'session'. eina_debug.c 440

Выделили память с помощью функции calloc и сразу её используем.

Ещё пример:
static Reference *
_entry_reference_add(Entry *entry, Client *client,
                     unsigned int client_entry_id)
{
   Reference *ref;

   // increase reference for this file
   ref = malloc(sizeof(*ref));
   ref->client = client;
   ref->entry = entry;
   ref->client_entry_id = client_entry_id;
   ref->count = 1;
   entry->references = eina_list_append(entry->references, ref);

   return ref;
}

Предупреждение PVS-Studio: V522 There might be dereferencing of a potential null pointer 'ref'. evas_cserve2_cache.c 1404

И вот так 563 раза. В статье я их привести не могу. Даю ссылку на файл: EFL_V522.txt.

V547 (39 ошибок)


static void
_ecore_con_url_dialer_error(void *data, const Efl_Event *event)
{
   Ecore_Con_Url *url_con = data;
   Eina_Error *perr = event->info;
   int status;

   status = 
     efl_net_dialer_http_response_status_get(url_con->dialer);

   if ((status < 500) && (status > 599))
   {
      DBG("HTTP error %d reset to 1", status);
      status = 1; /* not a real HTTP error */
   }
 
   WRN("HTTP dialer error url='%s': %s",
       efl_net_dialer_address_dial_get(url_con->dialer),
       eina_error_msg_get(*perr));

   _ecore_con_event_url_complete_add(url_con, status);
}

Предупреждение PVS-Studio: V547 Expression '(status < 500) && (status > 599)' is always false. ecore_con_url.c 351

Правильный вариант проверки должен быть таким:
if ((status < 500) || (status > 599))

Фрагмент кода с этой ошибкой скопирован ещё в 2 места:
  • V547 Expression '(status < 500) && (status > 599)' is always false. ecore_con_url.c 658
  • V547 Expression '(status < 500) && (status > 599)' is always false. ecore_con_url.c 1340

Следующая ошибочная ситуация:
EAPI void
eina_rectangle_pool_release(Eina_Rectangle *rect)
{
  Eina_Rectangle *match;  
  Eina_Rectangle_Alloc *new;
  ....
  match = (Eina_Rectangle *) (new + 1);
  if (match)
    era->pool->empty = _eina_rectangle_skyline_list_update(
                          era->pool->empty, match);
  ....
}

Предупреждение PVS-Studio: V547 Expression 'match' is always true. eina_rectangle.c 798

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

Указатель match может стать равен нулю, только если при сложении произойдёт переполнение. Однако переполнение указателя считается неопределённым поведением, поэтому такой вариант не стоит рассматривать.

И ещё один случай.
EAPI const void *
evas_object_smart_interface_get(const Evas_Object *eo_obj,
                                const char *name)
{
  Evas_Smart *s;
  ....
  s = evas_object_smart_smart_get(eo_obj);
  if (!s) return NULL;

  if (s)
  ....
}

Предупреждение PVS-Studio: V547 Expression 's' is always true. evas_object_smart.c 160

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

Прочие ошибки: EFL_V547.txt.

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

V556 (8 ошибок)


Все 8 ошибок выданы на один фрагмент кода. Для начала посмотрим на объявление двух перечислений.
typedef enum _Elm_Image_Orient_Type
{
  ELM_IMAGE_ORIENT_NONE = 0,
  ELM_IMAGE_ORIENT_0 = 0,
  ELM_IMAGE_ROTATE_90 = 1,
  ELM_IMAGE_ORIENT_90 = 1,
  ELM_IMAGE_ROTATE_180 = 2,
  ELM_IMAGE_ORIENT_180 = 2,
  ELM_IMAGE_ROTATE_270 = 3,
  ELM_IMAGE_ORIENT_270 = 3,
  ELM_IMAGE_FLIP_HORIZONTAL = 4,
  ELM_IMAGE_FLIP_VERTICAL = 5,
  ELM_IMAGE_FLIP_TRANSPOSE = 6,
  ELM_IMAGE_FLIP_TRANSVERSE = 7
} Elm_Image_Orient;

typedef enum
{
  EVAS_IMAGE_ORIENT_NONE = 0,
  EVAS_IMAGE_ORIENT_0 = 0,
  EVAS_IMAGE_ORIENT_90 = 1,
  EVAS_IMAGE_ORIENT_180 = 2,
  EVAS_IMAGE_ORIENT_270 = 3,
  EVAS_IMAGE_FLIP_HORIZONTAL = 4,
  EVAS_IMAGE_FLIP_VERTICAL = 5,
  EVAS_IMAGE_FLIP_TRANSPOSE = 6,
  EVAS_IMAGE_FLIP_TRANSVERSE = 7
} Evas_Image_Orient;

Как вы видите, названия констант в этих перечислениях похожи. Это и подвело программиста.
EAPI void
elm_image_orient_set(Evas_Object *obj, Elm_Image_Orient orient)
{
  Efl_Orient dir;
  Efl_Flip flip;

  EFL_UI_IMAGE_DATA_GET(obj, sd);
  sd->image_orient = orient;

  switch (orient)
  {
    case EVAS_IMAGE_ORIENT_0:
    ....
    case EVAS_IMAGE_ORIENT_90:
    ....
    case EVAS_IMAGE_FLIP_HORIZONTAL:
    ....
    case EVAS_IMAGE_FLIP_VERTICAL:
    ....
}

Предупреждения PVS-Studio:
  • V556 The values of different enum types are compared: switch(ENUM_TYPE_A) { case ENUM_TYPE_B:… }. efl_ui_image.c 2141
  • V556 The values of different enum types are compared: switch(ENUM_TYPE_A) { case ENUM_TYPE_B:… }. efl_ui_image.c 2145
  • V556 The values of different enum types are compared: switch(ENUM_TYPE_A) { case ENUM_TYPE_B:… }. efl_ui_image.c 2149
  • V556 The values of different enum types are compared: switch(ENUM_TYPE_A) { case ENUM_TYPE_B:… }. efl_ui_image.c 2153
  • V556 The values of different enum types are compared: switch(ENUM_TYPE_A) { case ENUM_TYPE_B:… }. efl_ui_image.c 2157
  • V556 The values of different enum types are compared: switch(ENUM_TYPE_A) { case ENUM_TYPE_B:… }. efl_ui_image.c 2161
  • V556 The values of different enum types are compared: switch(ENUM_TYPE_A) { case ENUM_TYPE_B:… }. efl_ui_image.c 2165
  • V556 The values of different enum types are compared: switch(ENUM_TYPE_A) { case ENUM_TYPE_B:… }. efl_ui_image.c 2169

Восемь раз сравниваются сущности, относящиеся к разным перечислениям.

При этом, благодаря везению, сравнения работаю правильно. Константы совпадают:
  • ELM_IMAGE_ORIENT_NONE = 0; EVAS_IMAGE_ORIENT_NONE = 0,
  • ELM_IMAGE_ORIENT_0 = 0; EVAS_IMAGE_ORIENT_0 = 0
  • ELM_IMAGE_ROTATE_90 = 1; EVAS_IMAGE_ORIENT_90 = 1
  • и так далее.

Функция будет работать правильно, но всё равно это ошибки.

Comment by Carsten Haitzler. All of the above orient/rotate enum stuff is intentional. We had to cleanup duplication of enums and we ensured they had the same values so they were interchangeable — we moved from rotate to orient and kept the compatibility. It's part of our move over to the new object system and a lot of code auto-generation etc. that is still underway and beta. It's not an error but intended to do this as part of transitioning, so it's a false positive.

Комментарий Андрей Карпова. Здесь и в некоторых других местах я не согласен, что это false positives. По такой логике получается, что предупреждение для неправильного, но по какой-то причине работающего кода, является ложным срабатыванием.

V558 (4 ошибки)


accessor_iterator& operator++(int)
{
  accessor_iterator tmp(*this);
  ++*this;
  return tmp;
}

Предупреждение PVS-Studio: V558 Function returns the reference to temporary local object: tmp. eina_accessor.hh 519

Чтобы исправить код, надо убрать & из объявления функции:
accessor_iterator operator++(int)

Другие ошибки:
  • V558 Function returns the reference to temporary local object: tmp. eina_accessor.hh 535
  • V558 Function returns the reference to temporary local object: tmp. eina_accessor.hh 678
  • V558 Function returns the reference to temporary local object: tmp. eina_accessor.hh 694


V560 (32 ошибок)


static unsigned int read_compressed_channel(....)
{
  ....
  signed char headbyte;
  ....
  if (headbyte >= 0)
  {
    ....
  }
  else if (headbyte >= -127 && headbyte <= -1)     // <=
  ....
}

Предупреждение PVS-Studio: V560 A part of conditional expression is always true: headbyte <= — 1. evas_image_load_psd.c 221

Если переменная headbyte была >= 0, то нет смысла выполнять проверку <= -1.

Рассмотрим другой случай.
static Eeze_Disk_Type
_eeze_disk_type_find(Eeze_Disk *disk)
{
  const char *test;
  ....
  test = udev_device_get_property_value(disk->device, "ID_BUS");
  if (test)
  {
    if (!strcmp(test, "ata")) return EEZE_DISK_TYPE_INTERNAL;
    if (!strcmp(test, "usb")) return EEZE_DISK_TYPE_USB;
    return EEZE_DISK_TYPE_UNKNOWN;
  }
  if ((!test) && (!filesystem))                     // <=
  ....
}

Предупреждение PVS-Studio: V560 A part of conditional expression is always true: (!test). eeze_disk.c 55

Второе условие избыточно. Если указатель test был ненулевым, то функция бы уже завершила свою работу.

Другие ошибки: EFL_V560.txt.

V568 (3 ошибки)


EOLIAN static Eina_Error
_efl_net_server_tcp_efl_net_server_fd_socket_activate(....)
{
  ....
  struct sockaddr_storage *addr;
  socklen_t addrlen;
  ....
  addrlen = sizeof(addr);
  if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0)
  ....
}

Предупреждение PVS-Studio: V568 It's odd that 'sizeof()' operator evaluates the size of a pointer to a class, but not the size of the 'addr' class object. efl_net_server_tcp.c 192

У меня есть сильное подозрение, что следовало вычислять не размер указателя, а размер структуры. Тогда код должен быть таким:
addrlen = sizeof(*addr);

Другие ошибки:
  • V568 It's odd that 'sizeof()' operator evaluates the size of a pointer to a class, but not the size of the 'addr' class object. efl_net_server_udp.c 228
  • V568 It's odd that 'sizeof()' operator evaluates the size of a pointer to a class, but not the size of the 'addr' class object. efl_net_server_unix.c 198


V571 (6 ошибок)


EAPI void eeze_disk_scan(Eeze_Disk *disk)
{
  ....
  if (!disk->cache.vendor)
    if (!disk->cache.vendor)
      disk->cache.vendor = udev_device_get_sysattr_value(....);
  ....
}

Предупреждение PVS-Studio: V571 Recurring check. The 'if (!disk->cache.vendor)' condition was already verified in line 298. eeze_disk.c 299

Лишняя или неправильная проверка.

Другие ошибки:
  • V571 Recurring check. The 'if (!disk->cache.model)' condition was already verified in line 302. eeze_disk.c 303
  • V571 Recurring check. The 'if (priv->last_buffer)' condition was already verified in line 150. emotion_sink.c 152
  • V571 Recurring check. The 'if (pd->editable)' condition was already verified in line 892. elm_code_widget.c 894
  • V571 Recurring check. The 'if (mnh >= 0)' condition was already verified in line 279. els_box.c 281
  • V571 Recurring check. The 'if (mnw >= 0)' condition was already verified in line 285. els_box.c 287

Примечание. Carsten Haitzler не считает это ошибками. Он считает подобные сообщения рекомендациями по микрооптимизации. А я считаю, что этот код неверен и его следует править. С моей точки зрения, это ошибки. Здесь у нас несогласие, как интерпретировать сообщения анализатора.

V575 (126 ошибок)


Диагностика срабатывает, когда функции передают странные фактические аргументы. Рассмотрим несколько вариантов срабатываний этой диагностики.
static void
free_buf(Eina_Evlog_Buf *b)
{
   if (!b->buf) return;
   b->size = 0;
   b->top = 0;
# ifdef HAVE_MMAP
   munmap(b->buf, b->size);
# else
   free(b->buf);
# endif
   b->buf = NULL;
}

Предупреждение PVS-Studio: V575 The 'munmap' function processes '0' elements. Inspect the second argument. eina_evlog.c 117

Вначале в переменную b->size записали 0, а затем передали ее в функцию munmap.

Мне кажется, надо было написать так:
static void
free_buf(Eina_Evlog_Buf *b)
{
   if (!b->buf) return;
   b->top = 0;
# ifdef HAVE_MMAP
   munmap(b->buf, b->size);
# else
   free(b->buf);
# endif
   b->buf = NULL;
   b->size = 0;
}

Продолжим.
EAPI Eina_Bool
eina_simple_xml_parse(....)
{
  ....
  else if ((itr + sizeof("") - 1 < itr_end) &&
           (!memcmp(itr + 2, "", sizeof("") - 1)))
  ....
}

Предупреждение PVS-Studio: V575 The 'memcmp' function processes '0' elements. Inspect the third argument. eina_simple_xml_parser.c 355

Непонятно, зачем сравнивать 0 байт памяти.

Продолжим.
static void
_edje_key_down_cb(....)
{
  ....
  char *compres = NULL, *string = (char *)ev->string;
  ....
  if (compres)
  {
    string = compres;
    free_string = EINA_TRUE;
  }
  else free(compres);
  ....
}

Предупреждение PVS-Studio: V575 The null pointer is passed into 'free' function. Inspect the first argument. edje_entry.c 2306

Если указатель compress нулевой, то и не надо освобождать память. Строчку
else free(compres);

можно удалить.

Comment by Carsten Haitzler. Not a bug but indeed some extra if paranoia like code that isn't needed. Micro optimizations again?

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

Аналогично:
  • V575 The null pointer is passed into 'free' function. Inspect the first argument. efl_ui_internal_text_interactive.c 1022
  • V575 The null pointer is passed into 'free' function. Inspect the first argument. edje_cc_handlers.c 15962

Но больше всего срабатываний диагностики V575 связано с использованием потенциально нулевых указателей. В общем-то эти ошибки аналогичны тем, что мы рассматривали, говоря о диагностике V522.
static void _fill_all_outs(char **outs, const char *val)
{
  size_t vlen = strlen(val);
  for (size_t i = 0; i < (sizeof(_dexts) / sizeof(char *)); ++i)
  {
    if (outs[i])
      continue;
    size_t dlen = strlen(_dexts[i]);
    char *str = malloc(vlen + dlen + 1);
    memcpy(str, val, vlen);
    memcpy(str + vlen, _dexts[i], dlen);
    str[vlen + dlen] = '\0';
    outs[i] = str;
  }
}

Предупреждение PVS-Studio: V575 The potential null pointer is passed into 'memcpy' function. Inspect the first argument. main.c 112

Используем указатель, не проверив, удалось ли выделить память.

Другие ошибки: EFL_V575.txt.

V587 (2 ошибки)


void
_ecore_x_event_handle_focus_in(XEvent *xevent)
{
  ....
   e->time = _ecore_x_event_last_time;
   _ecore_x_event_last_time = e->time;
  ....
}

Предупреждение PVS-Studio: V587 An odd sequence of assignments of this kind: A = B; B = A;. Check lines: 1006, 1007. ecore_x_events.c 1007

Comment by Carsten Haitzler. Not bugs as such — looks like just overzealous storing of last timestamp. This is adding a timestamp to an event when no original timestamp exists so we can keep a consistent structure for events with timestamps, but it is code clutter and a micro optimization.

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

Ещё одна ошибка: V587 An odd sequence of assignments of this kind: A = B; B = A;. Check lines: 1050, 1051. ecore_x_events.c 1051

V590 (3 ошибки)


static int command(void)
{
  ....
  while (*lptr == ' ' && *lptr != '\0')
    lptr++; /* skip whitespace */
  ....
}

Предупреждение PVS-Studio: V590 Consider inspecting the '* lptr == ' ' && * lptr != '\0'' expression. The expression is excessive or contains a misprint. embryo_cc_sc2.c 944

Избыточная проверка. Её можно упростить:
while (*lptr == ' ')

Ещё два аналогичных предупреждения:
  • V590 Consider inspecting the 'sym->ident == 9 || sym->ident != 10' expression. The expression is excessive or contains a misprint. embryo_cc_sc3.c 1782
  • V590 Consider inspecting the '* p == '\n' || * p != '\"'' expression. The expression is excessive or contains a misprint. cpplib.c 4012


V591 (1 ошибка)


_self_type& operator=(_self_type const& other)
{
  _base_type::operator=(other);
}

Предупреждение PVS-Studio: V591 Non-void function should return a value. eina_accessor.hh 330

V595 (4 ошибок)


static void
eng_image_size_get(void *engine EINA_UNUSED, void *image,
                   int *w, int *h)
{
   Evas_GL_Image *im;
   if (!image)
     {
        *w = 0;                                    // <=
        *h = 0;                                    // <=
        return;
     }
   im = image;
   if (im->orient == EVAS_IMAGE_ORIENT_90 ||
       im->orient == EVAS_IMAGE_ORIENT_270 ||
       im->orient == EVAS_IMAGE_FLIP_TRANSPOSE ||
       im->orient == EVAS_IMAGE_FLIP_TRANSVERSE)
     {
        if (w) *w = im->h;
        if (h) *h = im->w;
     }
   else
     {
        if (w) *w = im->w;
        if (h) *h = im->h;
     }
}

Предупреждения PVS-Studio:
  • V595 The 'w' pointer was utilized before it was verified against nullptr. Check lines: 575, 585. evas_engine.c 575
  • V595 The 'h' pointer was utilized before it was verified against nullptr. Check lines: 576, 586. evas_engine.c 576

Проверки if (w) и if (h) подсказывают анализатору, что входные аргументы w и h могут быть равны NULL. Опасно, что в начале функции они используются без проверки.

Если вызвать функцию eng_image_size_get вот так:
eng_image_size_get(NULL, NULL, NULL, NULL);

то она окажется к этому не готова и произойдёт разыменование нулевого указателя.

Сообщения, которые, как я думаю, также указывают на ошибки:
  • V595 The 'cur->node' pointer was utilized before it was verified against nullptr. Check lines: 9889, 9894. evas_object_textblock.c 9889
  • V595 The 'subtype' pointer was utilized before it was verified against nullptr. Check lines: 2200, 2203. eet_data.c 2200


V597 (6 ошибок)


EAPI Eina_Binbuf *
emile_binbuf_decipher(Emile_Cipher_Algorithm algo,
                      const Eina_Binbuf *data,
                      const char *key,
                      unsigned int length)
{
  ....
  Eina_Binbuf *result = NULL;
  unsigned int *over;
  EVP_CIPHER_CTX *ctx = NULL;
  unsigned char ik[MAX_KEY_LEN];
  unsigned char iv[MAX_IV_LEN];
  ....
on_error:
   memset(iv, 0, sizeof (iv));
   memset(ik, 0, sizeof (ik));

   if (ctx)
     EVP_CIPHER_CTX_free(ctx);

   eina_binbuf_free(result);

   return NULL;
}

Предупреждения PVS-Studio:
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'iv' buffer. The memset_s() function should be used to erase the private data. emile_cipher_openssl.c 293
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'ik' buffer. The memset_s() function should be used to erase the private data. emile_cipher_openssl.c 294

Я уже много раз писал в статьях, почему компилятор может удалить в таком коде вызовы функций memset. Не хочется повторяться. Если кто-то ещё не знаком с этим вопросом, предлагаю ознакомиться с описанием диагностики V597.

Comment by Carsten Haitzler. Above 2 — totally familiar with the issue. The big problem is memset_s is not portable or easily available, thus why we don't use it yet. You have to do special checks for it to see if it exists as it does not exist everywhere. Just as a simple example add AC_CHECK_FUNCS([memset_s]) to your configure.ac and memset_s is not found you have to jump through some more hoops like define __STDC_WANT_LIB_EXT1__ 1 before including system headers… and it's still not declared. On my pretty up to date Arch system memset_s is not defined by any system headers, same on debian testing… warning: implicit declaration of function 'memset_s'; did you mean memset'? [-Wimplicit-function-declaration], and then compile failure… no matter what I do. A grep -r of all my system includes shows no memset_s declared… so I think advising people to use memset_s is only a viable advice if its widely available and usable. Be aware of this.

Другие ошибки:
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'key_material' buffer. The memset_s() function should be used to erase the private data. emile_cipher_openssl.c 144
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'iv' buffer. The memset_s() function should be used to erase the private data. emile_cipher_openssl.c 193
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'ik' buffer. The memset_s() function should be used to erase the private data. emile_cipher_openssl.c 194
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'key_material' buffer. The memset_s() function should be used to erase the private data. emile_cipher_openssl.c 249


V609 (1 ошибка)


Для начала рассмотрим функцию eina_value_util_type_size.
static inline size_t
eina_value_util_type_size(const Eina_Value_Type *type)
{
   if (type == EINA_VALUE_TYPE_INT)
     return sizeof(int32_t);
   if (type == EINA_VALUE_TYPE_UCHAR)
     return sizeof(unsigned char);
   if ((type == EINA_VALUE_TYPE_STRING) ||
       (type == EINA_VALUE_TYPE_STRINGSHARE))
     return sizeof(char*);
   if (type == EINA_VALUE_TYPE_TIMESTAMP)
     return sizeof(time_t);
   if (type == EINA_VALUE_TYPE_ARRAY)
     return sizeof(Eina_Value_Array);
   if (type == EINA_VALUE_TYPE_DOUBLE)
     return sizeof(double);
   if (type == EINA_VALUE_TYPE_STRUCT)
     return sizeof(Eina_Value_Struct);
   return 0;
}

Обратите внимание, что функция может вернуть 0. Теперь посмотрим, как эта функция используется:
static inline unsigned int
eina_value_util_type_offset(const Eina_Value_Type *type,
                            unsigned int base)
{
   unsigned size, padding;
   size = eina_value_util_type_size(type);
   if (!(base % size))
     return base;
   padding = ( (base > size) ? (base - size) : (size - base));
   return base + padding;
}

Предупреждение PVS-Studio: V609 Mod by zero. Denominator range [0..24]. eina_inline_value_util.x 60

Потенциальное деление на ноль. Я не знаю возможна ли в реальности ситуация, когда функция eina_value_util_type_size вернёт здесь 0. Но в любом случае код опасен.

Comment by Carsten Haitzler. The 0 return would only happen if you have provided totally invalid input, like again strdup(NULL)… So I call this a false positive as you cant have an eina_value generic value that is not valid without bad stuff happening — validate you passes a proper value in first. eina_value is performance sensitive btw so every check here costs something. it's like adding if() checks to the add opcode.

V610 (1 ошибка)


void fetch_linear_gradient(....)
{
  ....
  if (t + inc*length < (float)(INT_MAX >> (FIXPT_BITS + 1)) &&
      t+inc*length > (float)(INT_MIN >> (FIXPT_BITS + 1)))
  ....
}

Предупреждение PVS-Studio: V610 Unspecified behavior. Check the shift operator '>>'. The left operand '(- 0x7fffffff — 1)' is negative. ector_software_gradient.c 412

V614 (1 ошибка)


extern struct tm *gmtime (const time_t *__timer)
  __attribute__ ((__nothrow__ , __leaf__));

static void
_set_headers(Evas_Object *obj)
{
  static char part[] = "ch_0.text";
  int i;
  struct tm *t;
  time_t temp;
  ELM_CALENDAR_DATA_GET(obj, sd);

  elm_layout_freeze(obj);

  sd->filling = EINA_TRUE;

  t = gmtime(&temp);            // <=
  ....
}

Предупреждение PVS-Studio: V614 Uninitialized variable 'temp' used. Consider checking the first actual argument of the 'gmtime' function. elm_calendar.c 720

V621 (1 ошибка)


static void
_opcodes_unregister_all(Eina_Debug_Session *session)
{
  Eina_List *l;
  int i;
  _opcode_reply_info *info = NULL;

  if (!session) return;
  session->cbs_length = 0;
  for (i = 0; i < session->cbs_length; i++)
    eina_list_free(session->cbs[i]);
  ....
}

Предупреждение PVS-Studio: V621 Consider inspecting the 'for' operator. It's possible that the loop will be executed incorrectly or won't be executed at all. eina_debug.c 405

V630 (2 ошибки)


Есть обыкновенный класс btVector3 с конструктором. Впрочем, этот конструктор ничего не делает.
class btVector3
{
public:
  ....
  btScalar m_floats[4];
  inline btVector3() { }
  ....
};

И есть вот такая структура Simulation_Msg:
typedef struct _Simulation_Msg Simulation_Msg;
struct _Simulation_Msg {
     EPhysics_Body *body_0;
     EPhysics_Body *body_1;
     btVector3 pos_a;
     btVector3 pos_b;
     Eina_Bool tick:1;
};

Обратите внимание, что некоторые члены класса имеют тип btVector3. Теперь посмотрим, как создаётся структура:
_ephysics_world_tick_dispatch(EPhysics_World *world)
{
   Simulation_Msg *msg;

   if (!world->ticked)
     return;

   world->ticked = EINA_FALSE;
   world->pending_ticks++;

   msg = (Simulation_Msg *) calloc(1, sizeof(Simulation_Msg));
   msg->tick = EINA_TRUE;
   ecore_thread_feedback(world->cur_th, msg);
}

Предупреждение PVS-Studio: V630 The 'calloc' function is used to allocate memory for an array of objects which are classes containing constructors. ephysics_world.cpp 299

Структура, содержащая в себе non-POD члены, создаётся с помощью вызова функции calloc.

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

Ещё одна ошибка: V630 The 'calloc' function is used to allocate memory for an array of objects which are classes containing constructors. ephysics_world.cpp 471

Comment by Carsten Haitzler. Because the other end of the pipe is C code that is passing around a raw ptr as the result from thread A to thread B, it's a mixed c and c++ environment. In the end we'd be sending raw ptr's around no matter what...

V654 (2 ошибки)


int
evas_mem_free(int mem_required EINA_UNUSED)
{
   return 0;
}

int
evas_mem_degrade(int mem_required EINA_UNUSED)
{
   return 0;
}

void * 
evas_mem_calloc(int size)
{
   void *ptr;

   ptr = calloc(1, size);
   if (ptr) return ptr;
   MERR_BAD();
   while ((!ptr) && (evas_mem_free(size))) ptr = calloc(1, size);
   if (ptr) return ptr;
   while ((!ptr) && (evas_mem_degrade(size))) ptr = calloc(1, size);
   if (ptr) return ptr;
   MERR_FATAL();
   return NULL;
}

Предупреждения PVS-Studio:
  • V654 The condition '(!ptr) && (evas_mem_free(size))' of loop is always false. main.c 44
  • V654 The condition '(!ptr) && (evas_mem_degrade(size))' of loop is always false. main.c 46

Видимо это какой-то недописанный код.

Comment by Carsten Haitzler. Old old code because caching was implemented, so it was basically a lot of NOP's waiting to be filled in. since evas speculatively cached data (megabytes of it) the idea was that if allocs fail — free up some cache and try again… if that fails then actually try nuke some non-cached data that could be reloaded/rebuilt but with more cost… and only fail after that. But because of overcommit this didn't end up practical as allocs would succeed then just fall over often enough if you did hit a really low memory situation, so I gave up. it's not a bug. it's a piece of history :).

Следующий случай более интересный и непонятный.

WTF

EAPI void evas_common_font_query_size(....)
{
  ....
  size_t cluster = 0;
  size_t cur_cluster = 0;
  ....
  do
  {
    cur_cluster = cluster + 1;
    
    glyph--;
    if (cur_w > ret_w)
    {
      ret_w = cur_w;
    }
  }
  while ((glyph > first_glyph) && (cur_cluster == cluster));
  ....
}

Предупреждение PVS-Studio: V654 The condition of loop is always false. evas_font_query.c 376

В цикле выполняется вот это присваивание:
cur_cluster = cluster + 1;

Это означает, что сравнение (cur_cluster == cluster) всегда вычисляется как false.

Comment by Carsten Haitzler. Above… it seems you built without harfbuzz support… we highly don't recommend that. it's not tested. Building without basically nukes almost all of the interesting unicode/intl support for text layout. You do have to explicitly — disable it… because with harfbuzz support we have opentype enabled and a different bit of code is executed due to ifdefs… if you actually check history of the code before adding opentype support it didn't loop over clusters at all or even glyphs… so really the ifdef just ensures the loop only loops one and avoids more ifdefs later in the loop conditions making the code easier to maintain — beware the ifdefs!

V668 (21 ошибка)


Как мы узнали ранее, в коде имеются сотни мест, где отсутствует проверка указателя после выделения памяти с помощью функции malloc / calloc.

На фоне этого проверки после использования оператора new выглядят как какая-то шутка.



Есть безобидные ошибки:
static EPhysics_Body *
_ephysics_body_rigid_body_add(....)
{
  ....
  motion_state = new btDefaultMotionState();
  if (!motion_state)
  {
    ERR("Couldn't create a motion state.");
    goto err_motion_state;
  }
  ....
}

Предупреждение PVS-Studio: V668 There is no sense in testing the 'motion_state' pointer against null, as the memory was alloc

Смок тестирование на небольшом проекте: как началось и какие результаты

Понедельник, 31 Июля 2017 г. 16:46 + в цитатник
Разрабатываю проект на C++. Решил попробовать на своем проекте тестовые сценарии
скриптовать на Python вместо того, чтобы тестировать код вручную. Обычно от программистов у нас в компании это не требуется, так что это был эксперимент. За год написал около 100 тестов и этот эксперимент оказался вполне полезным. Тесты выполняются несколько минут и позволяют быстро проверить как мои пул реквесты так и пул реквесты других разработчиков.

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

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

Однако на задачах с длительной разработкой соотношение уже другое. Один раз написанный
автоматический тест дает экономию времени, поскольку используется много раз в процессе разработки. Например, в ходе разработки одной задачи был написан 18 тестов, и именно они гарантировали корректность алгоритма, который состоял из C++, Lua, SQL и использовал обмен сообщения с RabbitMQ и работу с БД.

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

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

https://habrahabr.ru/post/334544/


Метки:  

Artisto: опыт запуска нейросетей в production

Понедельник, 31 Июля 2017 г. 16:33 + в цитатник


Эдуард Тянтов (Mail.ru Group)


Меня зовут Эдуард Тянтов, я занимаюсь машинным обучением в компании Mail.ru Group. Я расскажу про приложение стилизации видео с помощью нейронных сетей Artisto, про технологию, которая лежит в основе этого приложения.

Давайте я дам пару фактов о нашем приложении:

  • 1-е мобильное приложение стилизации видео в мире;
  • Уникальная технология стабилизации видео;
  • Приложение с технологией разработаны за 1 месяц.

Во-первых, это самое первое в мире приложение для стилизации видео в мире. До этого были только стартапы вроде deepart.io – компания по сей день существует и предлагает, например, сконвертировать 10 сек. видео за 100 евро. А мы всем пользователям предлагаем сделать это абсолютно бесплатно в онлайне в отличие от deepart.io, которые делают это в оффлайне.

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

И третье – мы всего лишь за один месяц, с того момента как мы сели за разработку технологии и до того момента, когда приложение вышло в store‘ы. Прошел всего один месяц, что достаточно оперативно.



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



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




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

В 90-х годах Ян Ли Кун (он сейчас руководитель исследовательской лаборатории Facebook FAIR) активно работал над созданием сверточных сетей. В 98-м году он выпустил работу, где рассказал подробно, как с помощью таких сетей можно распознавать рукописные цифры на чеках, и некоторые банки в США применяли эту технологию на практике. Но тогда эта технология сверточных сетей особой популяризации не получила из-за того, что нужно было достаточно долго и трудно их обучать, и нужно было много вычислений. Прорыв случился много позже, и об этом я расскажу.



Давайте подробно обсудим, что такое сверточная сеть. Она получила свое название за счет операции свертки, как ни странно. Ее смысл заключается в следующем – мы берем изображение RGB (или можно grey scale), это 3 канала, 3 матрицы. Мы эти матрицы прогоняем через свертку. Свертка, как показано внизу на слайде – это матрица 3 х 3 в данном случае, которую мы двигаем по изображению и поэлементно умножаем веса на этой свертке на числа соответствующие и складываем, получаем соответствующие позиции, некие числа. Получается, что мы входную матрицу преобразовали в выходную. Эта выходная матрица называется feature maps. И, если рассказать, что графически кодирует свертка, она кодирует какие-то признаки на изображении. Это могут быть, например, наклонные линии. Тогда в feature map мы будем иметь информацию, где, в каких местах эта наклонная линия имеется на изображении. Естественно, в сверточных нейронных сетях этих сверток очень-очень много, тысячи, и они кодируют различные признаки. Что самое замечательно в этом всем – нам абсолютно не нужно задавать их, т.е. они в процессе обучения выучиваются сами.



Второй важный блок – это pooling или операция subsampling. Она признана сократить размерность входной матрицы, входного слоя. Как показано на рисунке, мы берем матрицу 4 х 4 и по 4 элемента берем максимальные, т.е. получаем матрицу 2 х 2. Т.о. мы существенно сокращаем, а именно в 4 раза, все последующие вычисления. Это нам также, как бонус, дает некую устойчивость к перемещению поворотов объектов на картинке, потому что в сверточных нейросетях много операций pooling. В результате к концу сети немного теряются пространственные координаты, и получается некая независимость от положения.

Если мы посмотрим, что сверточная сеть выучивает, то получится достаточно интересно. Данная сеть училась на лицах:



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

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



Основной прогресс в компьютерном зрении происходил в рамках ImageNet Challenge. ImageNet – это такая база фотографий, размеченная руками, там их примерно 10 млн. Она была специально создана для того, чтобы исследователи со всего мира соревновались и прогрессировали в этом computer vision’е.

Есть популярный challenge – с 2010-го года проходят ежегодно соревнования, там много разных категорий и самая популярная – это классификация на 1000 классов изображения. Среди классов много животных, разных пород собак и т.д. В 2012-ом году впервые на этом соревновании победила сверточная сеть, ее автор Крижевский (Krizhevsky A.) смог поместить все вычисления на GPU, их достаточно много и благодаря этому, он смог обучить 5-слойную сеть. На тот момент это было существенным прорывом, как для сверточных сетей, как и для computer vision, потому что существенно сократило ошибку.

Ошибка там измеряется топ 5, т.е. сеть выдает 5 возможных категорий, что изображено на картинке, и если хоть одна совпадает, то считается успехом. Что очень интересно – ученые замерили, сколько люди ошибаются на этом challenge. Посадили людей, дали им точно так же размечать данные и выяснилось, что ошибка порядка 5%. А современные сверточные сети имеют ошибку порядка 3.5%. Это не совсем значит, что нейросети работают лучше, чем наш мозг, потому что они были заточены именно под эти тысячи классов, а люди не всегда различают породы собак, и если человека посадить и его учить этим классам, то, естественно, будет лучше, но, тем не менее, уже алгоритм очень и очень близко.



Рассмотрим популярную архитектуру. В данном случае это VGG. Это один из победителей 2014 года. Она очень популярна, а популярна потому, что последователи обученную ее выложили в сеть, и все ей могли пользоваться. Она выглядит, вроде, сложно, но на самом деле она просто блочная, каждый блок сверточный. Это несколько слоев сверток и потом операция pooling’а, т.е. уменьшения изображения. И так у нас 5 раз повторяется, у нас происходит 5 pooling’ов. В конце у нас свертки уже видят изображение 14 х 14, и количество фильтров ближе к концу возрастает. Т.е. если мы вначале имеем 32 фильтра, которые распознают примитивы, то в конце сети у нас уже есть 512 фильтров, которые распознают какие-то сложные объекты, может быть, стулья, столы, животные и т.д. Дальше в этой сети идут полносвязные блоки, которые выполняют уже на основе этих признаков чисто классификацию на эти 1000 классов.

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

Давайте и перейдем к этой теме. Как перенести стиль на фото?



Во-первых, что мы хотим, чтобы быть предельно понятным? Мы хотим взять фотографию, в данном случае это город, и взять какой-то стиль, например, «De sterrennacht» Ван Гога и попытаться перерисовать исходную контентную картинку в этом стиле. И получить такое piece of art, как говорится. Кажется невероятным, но в современном мире это возможно. Т.е. нам не нужен уже никакой художник для этого, можно это сделать.



Немножко истории. В сентябре 2015 года Леон Гатис выпустил статью, в которой он рассказал, как это можно сделать, придумал. Единственным его недостатком было то, что этот алгоритм был достаточно медленным, и из-за этого широкого практического применения для конечного пользователя на тот момент не получил.

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

И дальше через пару месяцев выходит приложение Prisma, получает оглушительный успех, и через полтора месяца запускается Vinci от Вконтакте для стилизации фото, и от нас – Artisto для стилизации исключительно видео на тот момент.



Как же все это можно провернуть? Начнем с первой статьи Artistic Style. Как нам восстановить контент? Т.е. нам надо смешать контентную картинку и стилевую каким-то образом. Для начала нам надо попробовать восстановить контент. Что мы для этого будем делать? Мы возьмем нашу VGG сеть, о которой я упоминал ранее, и будем использовать ее как feature extractor, т.е. будем рассматривать ее как способ извлечения признаков из изображения. Мы прогоняем картинку через эту сеть и получаем на выходе иерархическую информацию в этих feature maps, которая нам говорит, что расположено на этом изображении. Соответственно, можем прогнать любую другую картинку, например, шум, получить какие-то числа, эти feature maps и между собой уже их можем сравнивать, т.е. теперь мы можем численно сравнивать картинки.



Какой алгоритм предложил Гатис. Он предложил такой оптимизационный алгоритм – мы начинаем с шума, это наш холст, на который мы пробуем нарисовать картинку, которая будет похожа на контентную. Прогоняем ее через сеть, получаем признаки и сравниваем, насколько они похожи с контентными признаками нашего целевого изображения. Замеряем ошибку. Естественно, вначале она огромная, потому что на шуме там ничего не сработало, нет никаких объектов. И эту ошибку обратным распространением ошибки – back propagation алгоритмом гоним по сети. Но сеть мы сами никак не изменяем, т.е. она у нас фиксированная, а ошибка к нам приходит в начало, в изображение. И мы заменяем на основе ошибки на выходе само изображение, т.е. перерисовываем. Так мы повторяем n итераций.



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

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



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

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

Переходим к самому интересному, как же все-таки перенести стиль? И что вообще такое стиль?



Кажется, что стиль – это такие мазки, цвета. Именно это хочется перенести. Если мы возьмем точно такой же алгоритм и попытаемся восстановить стиль, то у нас объекты, которые были на стилевом изображении, будут на тех же местах. Соответственно, первое, что нам нужно сделать и, по сути, единственное – это избавиться от пространственных координат. Естественно, если мы избавимся от координат, то мы уже как-то сможем передавать стиль.

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

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

Можно просто взять feature map. Мы зафиксировали какой-то слой, по которому мы хотим восстанавливать стиль, допустим, какое-то раннее из 2-го блока. Берем и по пространственным координатам усредняем. Т.е. получили такой вектор средних. Как это можно понять? Т.е. это реально будет восстанавливать стиль, мы сейчас это увидим. Почему это будет восстанавливать? Можно, например, предположить, что какая-то из сверток, например, кодирует признак наличия звезды, на изображении. На «De sterrennacht» у нас n звезд, допустим, 10. Если у нас в процессе оптимизации наш алгоритм нарисовал всего 2 звезды, а их там 10, то сравнение этих средних нам поможет понять, что надо дорисовать еще звезду, сделать небо более звездным.

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



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



Что мы здесь видим? Это шум в стиле Ван Гога. Цвета, мазки – все передалось.



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



Теперь, когда мы имеем 2 технологии восстановления стиля и контента, нам бы их как-нибудь смешать, чтобы лучше смиксовать 2 этих изображения. Как мы это делаем? Опять же, фиксируем стиль, получаем из него ковариационную матрицу, запомнили, да? Берем контентное изображение, извлекаем из него feature map, фиксируем.

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



И, о чудо, это действительно происходит, эта картинка из статьи взбудоражила всю общественность в deep learning’е. Это был прорыв. И уже позволяет без художника стилизовать любое изображение.



Итак, если резюмировать, то нам этот алгоритм позволяет смешивать любые 2 картинки, неважно какие. Хоть 2 фотографии. Не требует никакого обучения, здесь только оптимизация, поэтому можно достаточно быстро экспериментировать, подбирать нужные параметры и, в общем, очень быстро получать какой-то результат. Код на всех популярных библиотеках для deep learning’а есть, можно выбрать любую наиболее подходящую. Но самый большой жирный минус в том, что все это очень долго вычисляется для онлайн, т.е. если на CPU это 5 минут, если на GPU современном, то это порядка 10-15 сек. Еще фото можно постараться засунуть как-то в онлайн, для видео – нет.



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

Звучит просто. Как это мы делаем?



Опять же, фиксируем стилевое изображение, матрицу ковариации из него, контентное изображение и его признаки, и теперь у нас здесь отличие. Нам надо стилизовать любое изображение. Соответственно, мы берем некий dataset, мы будем обучать сеть. Мы берем dataset какой-то фотографии. Желательно для нашего приложения брать фотографии, которые реально будут загружать пользователи, а не какие-то абстрактные. Например, фотографии людей – пользователи очень любят фотографировать людей, себя, там они должны обязательно быть, чтобы сеть уметь на них конкретно отрисовывать. После этого мы берем какое-то изображение из этого dataset, прогоняем через нашу новую сеть-генератор, или трансформационную сеть, которая нам должна отстилизовать изображение. Вначале, когда мы ее инициализируем она выдает какой-то шум, но со временем она будет обучаться и выдавать какое-то стилизованное изображение. Т.к. нам надо сеть обучать, мы должны понять, насколько хорошо она стилизует. Делаем мы это точно так же – мы прогоняем это стилизованное изображения через сеть, получаем необходимые нам данные и сравниваем с тем контентом и стилем, которые мы хотим. Опять же, некоторые пропорции. И эту ошибку прогоняем обратно и уже не оптимизируем изображение, а именно обучаем саму сеть. И так мы ходим по этому dataset, и спустя десятки тысяч итераций сеть учится стилизовать изображение.



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



И если резюмировать, то огромный плюс – это то, что требуется всего 1 прогон по обученной сети. На GPU это достаточно быстро, в десятках мсек это измеряется, в зависимости от самой сети или входного разрешения картинки. Это, естественно, не бесплатно. Мы за это платим, потому что нам под каждый стиль нужно обучать отдельную сеть, обучаются сети достаточно долго, но в данной задаче это еще приемлемо – несколько часов, потому что обычно сети, та же VGG, обучаются, например, неделю. Здесь нам для экспериментов, а их нужно будет проводить очень много, нам нужно несколько часов на современных картах. И на момент того, как мы разрабатывали, код был только от Ульянова, он был на Torch, это библиотека Facebook, она на Lua, что для специалистов по машинному обучению несколько непривычно, потому что все привыкли к Python, в основном. И тут требуется некий входной порог.

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



Но прежде чем перейти к экспериментам, я должен познакомить вас с этой девушкой, которая участвовала во всех наших экспериментах и наши нейронные сети сильно коверкали ее прекрасное лицо. Специально для этой конференции, я ее «выдернул» просто из выдачи Google по запросу «девушка». Это оказывается жена футболиста, ее зовут Хофи и она у нас натерпелась. Общая рекомендация – не использовать фотографии себя или своих близких для тестирования, потому что дальше они будут попадать в презентации, и про вас будут рассказывать на HighLoad.



Если мы возьмем этот медленный алгоритм Гатиса (т.е. мы в фазе ресерча пробовали все) и применим некоторые стили, то мы получим что-то вполне адекватное достаточно быстро.



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

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



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



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



Из коробки оно не работает, т.е. не выдает красивого ничего, по-прежнему, из-за того что обучение идет несколько часов, очень трудно экспериментировать. Мы на это потратили достаточно много времени. А хороший результат получается только тогда, когда взять сеть из второй статьи – Джастина Джонсона из Стенфорда – и применить модель генератора оттуда. Она, оказывается, работает уже неплохо. Она имеет примерно такую структуру, похожа на инкодер/декодер, который применяется, в частности, для сегментации. Мы в первой половине сети так же, как VGG, у нас свертки, пулинги, и в конце мы получаем какое-то представление о том, что нарисовано на картинке, у нас достаточное, в несколько раз меньшее разрешение, а потом мы начинаем делать обратные операции, повышая разрешение, уменьшая количество фильтров, перерисовывая изображение. И на выходе мы получаем картинку.



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

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



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



Вторая проблем – это рябь. Здесь я привожу видео, которое я взял у Марка Цукерберга, они сейчас тоже с этим экспериментируют, и вы можете видеть, что там достаточно сильно рябит на пустых областях – на стене, на потолке. Связано это с тем, что алгоритму, оказывается, очень выгодно в эти пустые области накладывать текстуру. И текстуру он от кадра к кадру накладывает немного по-разному, и все рябит. Выгодно ему это делать, потому что, напомню, у нас контент loss, т.е. мы контент – сравниваем последние слои, где у нас объекты. На стене объектов никаких нет. А стилевое – это ранние слои, где какая-то текстура. И нейросеть, удовлетворяя стилевой loss, лепит туда текстуры, не создавая особо никаких объектов. Поздние слои контента ничего этого не видят и, соответственно, сеть безнаказанно все мостит текстурой. Это сильно ухудшает видео. Мы с этим много боролись, с этими двумя проблемами.



Первая из наших наработок – это т.н. heatmap loss. Мы берем какой-то контентный layer, и у нас получились какие-то признаки. Мы детектим какие-то объекты. Значит, если мы возьмем все эти 512, например, фильтров просуммируем, то мы получим оценку, насколько в той или иной части картины много объектов. Если мы видим стену, она плоская, там ничего нет интересного, синий цвет, там нет никаких объектов, мы их не видим. И если мы возьмем лицо девушки, особенно ее волосы, там уже много интересных фич, последние слои много чего интересного видят. Если мы возьмем плохую стилизацию, как снизу, и она начнет лепить нам текстуру, куда нам не нужно, получается совершенно другой heatmap. Мы можем сравнивать эти тепловые карты между собой, добавить еще один loss и штрафовать сеть, чтобы она не могла эту текстуру мостить там, где нам не нужно. Мы управляем обучением сети.



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



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



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



Четвертая вещь, что можно делать – это т.н. super-resolution. Такая задача в машинном обучении – повышение разрешения картинки. Она отлично подходит для того, чтобы убирать всякий мелкий шум и – здорово – есть всякие уже предобученные сети, например, waifu2x. Ее обучали для улучшения качества анимэ-картинок, но она отлично подходит и для обычных объектов. Но большой жирный минус, в том, что это еще одна нейронная сеть, она достаточно тяжелая и, по сути, это минимум в 2 раза уменьшение скорости.



Результаты, тем не менее, для super-resolution, для этой waifu, очень интересны. У нас картинка, которую мы стилизовали, получилась немного расфокусированная, мы прогоняем ее через waifu и получаем – у нас блюр убрался и, что интересно, перерисовалась шапка, перерисовались зрачки – такой вот эффект, т.е. мы можем улучшить картинку. Но для видео увеличение в 2 раза – это слишком жестко по нагрузке, для фото это, в принципе, реально.



Мы вот это не применяли. И мы взяли 3 из 4-х концепций — тепловые карты, дизайнеров и фрагментацию, т.е. добавление шума, чтобы сделать наш продукт лучше. Сейчас я вам покажу кусок видео, который мы сделали для руфера из Нью-Йорка. Вперед. (демонстрирует видео).



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

Теперь у нас есть технология, мы умеем ее готовить, пришло время поставить генерацию стилей на поток.



Тут есть парочка аспектов: во-первых, параметров много, их реально много. На следующем слайде я покажу, насколько. И вторая проблема, что результат стилизации нужно оценивать исключительно на глаз, причем не любой глаз, а человека, у которого есть стиль, чувство вкуса некое. Потому что пользователю то, что понравится диплёнеру, совершенно может не коррелировать никак. Может нравится, когда происходит отличная трансформация, все преколбашивает, все цвета, все по-другому, а пользователям нужно не это. Соответственно, что было замечено – по мере обучения наши стилевые контентные loss’ы не очень хорошо коррелируют с красотой изображения. С какого-то момента эти loss’ы падают, сеть обучается лучше и лучше, а красота начинает резко ухудшаться. Поэтому надо в какие-то моменты останавливаться, т.е. надо отсмотреть много результатов.

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



Тут кусок интерфейса. Вглядываться в эти параметры не нужно, просто для того, из скольких всяких разных параметров мы сделали интерфейс. Я вначале использовал коробочное решение FGLab на node.js, но оно у меня почему-то глючило, теряло некие эксперименты. Мы написали быстренько свое, несложный интерфейс.

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



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



Какой рецепт успешной штамповки стилей? Мы берем много стилевых изображений, все которые нам нравятся, надергиваем из Сети. Берем рабочие сеты гипер-параметров, которые у нас на каких-то стилях работали хорошо и давали результат. Дальше нам нужны вычислительные мощности, т.е. нам нужно много GPU. Мы использовали где-то 20 штук для обучения, но т.к. это требует вмешательства человека, то у нас занято было штук 5. Т.е. только в какие-то моменты мы использовали все. Необходимым элементом является Redbull – без него невозможно отсмотреть такое количество фотографий и не сойти с ума. И после просмотра мы получаем кучу стилей.

Немного о результатах нашего приложения. Выпустились мы в конце июля, в начале октября наше приложение завоевывает топы USA, т.е. в App Store и Play Market мы занимаем 2-ое место, в «десятке» мы висим порядка 2-х недель.



Это отличный результат, достаточно тяжело туда пробиться. В Apple Store 1-ое место мы уступили iTunes U – это приложение, которое Apple «прибил гвоздями» на несколько месяцев на первое место, и только недавно его оттуда вытеснили. Поэтому проиграли только им. Но на этапе лонча через неделю-две в России мы тоже занимали 1-ое место. По ходу жизни нашего приложения, особенно вирусного эффекта в США, у нас во многих разных странах, во всяких Гавайях и т.д. тоже были хорошие позиции и много 1-ых мест.



Второй интересный для нас результат – PR-эффект – то, что мы специально для nvidia разработали с помощью нашего приложения life-стриминг, и они на своей конференции GPU Technology демонстрируют возможности новых видеокарт для обработки видео. На картинке не видно, но президент презентует работу нашего приложения, отличный для нас PR.



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



Потом мы заметили, что пользователи наши начали использовать MSQRD и Snapchat для наложения масок и потом прогонять через Artisto, чтобы получить стилизованное фото или видео. У нас есть ICQ, у Mail.ru Group, и есть такая технология. Мы у них ее позаимствовали и впилили в наше приложение. И теперь пользователи могут одновременно делать обе вещи. Мы видим, что даже когда мы маску накладываем просто на фотографию, видно, что она наложена, если мы перерисовываем каким-то стилем, то уже это практически незаметно и получается очень интересные карды. Вы можете видеть меня на одной из этих картинок.

И в заключение скажу, что сейчас мы живем в такое время, когда нам вычислительные мощности алгоритма позволяют анализировать данные, обучать сложные сети – это все больше и больше проникает в нашу жизнь. В deep learning’е сейчас бум, это реально новое открытие, очень много сейчас внедрения этих технологий в сферу развлечений, таких как Artisto и прочих. Но уже очень скоро это будет непосредственно в нашей жизни в виде пилотируемых автомобилей. Я очень жду медицину, когда, например, те архитектуры сетей, которые мы сегодня рассматривали, можно использовать as is для анализа МРТ, для анализа УЗИ, они там тоже отлично работают. Поэтому, думаю, в ближайшие лет 5, уже можно будет заменять дорогостоящих специалистов, которых дорого и долго учить, и они еще ошибаются часто. Такая технология сможет заменить.

Если вас заинтересовали сверточные сети, то есть очень хороший курс Стенфорда на эту тему, который можно прочитать, там все коротко и ясно написано. Значит, если вы хотите поэкспериментировать со стилизацией, то можно вбить «Style Transfer» в Google – там будут все статьи, весь код, который можно установить и попробовать, как оно работает. Уверен, что в результате моего доклада теперь вы точно знаете, что за этими технологиями сверточных сетей, в частности стилизации, стоят достаточно простые и понятные концепции, ничего космического, магического там нет. Всеми этими технологиями, связанными с нейросетями, очень интересно заниматься, увлекательно и, как вы видели по ходу моей презентации, очень даже весело.

Спасибо большое!

Контакты


» Блог компании Mail.Ru Group

Этот доклад — расшифровка одного из лучших выступлений на профессиональной конференции разработчиков высоконагруженных систем HighLoad++.

Сейчас мы уже вовсю готовим конференцию 2017-года — самый большой HighLoad++
в истории. Если вам интересно и важна стоимость билетов — покупайте сейчас, пока цена ещё не высока!

Кстати, в этом году мы также будем проводить AI-секцию, посвящённую машинному обучению и нейронным сетям.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334530/


Метки:  

[Перевод] Собеседование для фронтенд-разработчика на JavaScript: самые лучшие вопросы

Понедельник, 31 Июля 2017 г. 16:15 + в цитатник

Метки:  

Как скрестить ежа с ужом. Используем GridView из Yii 2 в проекте на Laravel

Понедельник, 31 Июля 2017 г. 16:00 + в цитатник
Недавно была статья про Yii, где в комментариях обсуждали специфичные для Yii компоненты, в частности GridView и ActiveForm, и фреймворк Laravel. Я подумал, а почему бы и нет.
composer create-project laravel/laravel
...
composer require yiisoft/yii2


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



Какие есть варианты



https://github.com/view-components/grids
https://github.com/assurrussa/grid-view-table
https://github.com/dwightwatson/bootstrap-form
https://github.com/core-system/bootstrap-form
https://github.com/adamwathan/bootforms
https://github.com/zofe/rapyd-laravel

Основные требования:
— верстка Bootstrap
— автоматическая обработка сортировки, пагинации, ошибок валидации формы
— минимум кода, написанного вручную
— кастомизируемость

https://github.com/view-components/grids

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

https://github.com/assurrussa/grid-view-table

Много бойлерплейта, добавляет свою глобальную функцию, какой-то странный способ рендеринга.

https://github.com/dwightwatson/bootstrap-form

Форма сама выбирает роуты для action, ошибки берутся из сессии. Но в целом близко к тому, что нужно.

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

https://github.com/core-system/bootstrap-form

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

https://github.com/core-system/bootstrap-form

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

https://github.com/zofe/rapyd-laravel

Этот вариант кажется наиболее подходящим. Есть и грид, и формы. Грид вполне неплохой, но с формами проблема.
— Действия view/create/edit висят на одном роуте, различаются через get-параметр. Соответственно и в гриде по умолчанию URL для действий такие же.
— Это одна форма, просто различается режимом отображения. Это создает проблемы, если надо created_at/updated_at показывать только для view. И свой класс для поля надо описывать для всех 3 режимов.
— Не очень хороший код в проекте


Интеграция


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

Для справки, папка laravel занимает 2.6 Мб, папка symfony 4.6 Мб, папка yiisoft 3.9 Мб, зависимости Yii 5.6 Мб.

Рассмотрим простое приложение с заказами и товарами.

SQL
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `email` varchar(100) NOT NULL,
  `password` varchar(255) NOT NULL,
  `remember_token` varchar(100) DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `products` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `orders` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(10) unsigned NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `orders-users` (`user_id`),
  CONSTRAINT `orders-users` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `order_items` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `order_id` int(10) unsigned NOT NULL,
  `product_id` int(10) unsigned NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `order_items-orders` (`order_id`),
  KEY `order_items-products` (`product_id`),
  CONSTRAINT `order_items-orders` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `order_items-products` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB;



Создадим Eloquent модели и OrderController для раздела заказов. Создадим группу роутов для админки.
routes/web.php
Route::group(['prefix' => 'admin', 'as' => 'admin.', 'namespace' => 'Admin'], function () {
    Route::get('/order', 'OrderController@index')->name('order.index');
    Route::get('/order/view/{id}', 'OrderController@view')->name('order.view');
    Route::get('/order/create', 'OrderController@create')->name('order.create');
    Route::get('/order/update/{id}', 'OrderController@update')->name('order.update');
    Route::post('/order/create', 'OrderController@create');
    Route::post('/order/update/{id}', 'OrderController@update');
    Route::post('/order/delete/{id}', 'OrderController@delete')->name('order.delete');
});


Создадим Bootstrap-шаблон со ссылками на CDN.

resources/views/layouts/main.blade.php


    
        
        
        

        

        

        

        
        
        

        
    
    

        @include('layouts.nav')

        
@yield('content')



Делаем middleware с инициализацией и подключаем к роутам админки.
Инициализация выглядит так.
routes/web.php
$initYii2Middleware = function ($request, $next)
{
    define('YII_DEBUG', env('APP_DEBUG'));
    include '../vendor/yiisoft/yii2/Yii.php';
    spl_autoload_unregister(['Yii', 'autoload']);
    $config = [
        'id' => 'yii2-laravel',
        'basePath' => '../',
        'timezone' => 'UTC',
        'components' => [
            'assetManager' => [
                'basePath' => '@webroot/yii-assets',
                'baseUrl' => '@web/yii-assets',

                'bundles' => [
                    'yii\web\JqueryAsset' => [
                        'sourcePath' => null,
                        'basePath' => null,
                        'baseUrl' => null,
                        'js' => [],
                    ],
                ],
            ],
            'request' => [
                'class' => \App\Yii\Web\Request::class,
                'csrfParam' => '_token',
            ],
            'urlManager' => [
                'enablePrettyUrl' => true,
                'showScriptName' => false,
            ],
            'formatter' => [
                'dateFormat' => 'php:m/d/Y',
                'datetimeFormat' => 'php:m/d/Y H:i:s',
                'timeFormat' => 'php:H:i:s',
                'defaultTimeZone' => 'UTC',
            ],
        ],
    ];
    (new \yii\web\Application($config));  // initialization is in constructor
    Yii::setAlias('@bower', Yii::getAlias('@vendor') . DIRECTORY_SEPARATOR . 'bower-asset');

    return $next($request);
};

Route::group(['prefix' => 'admin', 'as' => 'admin.', 'namespace' => 'Admin', 'middleware' => $initYii2Middleware], function () {
    ...
});


spl_autoload_unregister(['Yii', 'autoload']); — лучше отключить, чтобы не мешался, достаточно автозагрузчиков Laravel. Он ищет файлы через getAlias('@'...) и конечно не находит.
basePath — корневая директория приложения, при неправильной установке могут быть ошибки в путях. В этой же директории создается папка runtime.
assetManager.basePath, assetManager.baseUrl — путь и URL для публикации ассетов, название папки произвольное.
assetManager.bundles — отключаем публикацию jQuery, так как она подключается в главном шаблоне отдельно.
request — переопределяем компонент запроса, в котором заменяем работу с CSRF-токеном, название поля такое же как в настройках Laravel.
urlManager.enablePrettyUrl — надо включить, если нужны дополнительные модули типа Gii.
(new \yii\web\Application($config)) — в конструкторе происходит присвоение Yii::$app = $this;

Компонент запроса выглядит так:
app/Yii/Web/Request.php
namespace App\Yii\Web;

class Request extends \yii\web\Request
{
    public function getCsrfToken($regenerate = false)
    {
        return \Session::token();
    }
}

Токеном управляет Laravel, поэтому регенерацию обрабатывать не надо.


Грид


Теперь можно попробовать запустить. Добавим код для списка заказов.

app/Http/Controllers/Admin/OrderController.php
public function index(Request $request)
{
    $allModels = Order::query()->get()->all();
    $gridViewConfig = [
        'dataProvider' => new \yii\data\ArrayDataProvider([
            'allModels' => $allModels,
            'pagination' => ['route' => $request->route()->uri(), 'defaultPageSize' => 10],
            'sort' => ['route' => $request->route()->uri(), 'attributes' => ['id']],
        ]),
        'columns' => [
            'id',
            'user.name',
            ['label' => 'Items', 'format' => 'raw', 'value' => function ($model) {
                $html = '';
                foreach ($model->items as $item) {
                    $html .= '
' . htmlspecialchars($item->product->name) . '
'; } return $html; }], 'created_at:datetime', 'updated_at:datetime', [ 'class' => \yii\grid\ActionColumn::class, 'urlCreator' => function ($action, $model, $key) use ($request) { $baseRoute = $request->route()->getName(); $baseRouteParts = explode('.', $baseRoute); $baseRouteParts[count($baseRouteParts) - 1] = $action; $route = implode('.', $baseRouteParts); $params = is_array($key) ? $key : ['id' => (string) $key]; return route($route, $params, false); } ], ], ]; return view('admin.order.index', ['gridViewConfig' => $gridViewConfig]); }


resources/views/admin/order/index.blade.php
@extends('layouts.main')

@section('title', 'Index')

@section('content')

    

Orders

{!! \yii\grid\GridView::widget($gridViewConfig) !!} @endsection



Нужно установить dataProvider.pagination.route и dataProvider.sort.route, иначе произойдет обращение к Yii::$app->controller->getRoute(), а контроллер у нас null. Аналогично с ActionColumn, только там будет проверка и InvalidParamException. URL генерируется через \yii\web\UrlManager, но результат получается такой же, как с роутингом Laravel. Можно задать менеджер через dataProvider.pagination.urlManager, если нужно.
Метки колонок пока оставим автогенерируемые.
Также надо задать некоторые стили для иконок сортировки.

Грид выводится, но так как нет фронтенд-скриптов, то кнопка Delete не работает.

Надо вывести скрипты, которые находятся в компоненте \yii\web\View. Методы renderHeadHtml(), renderBodyBeginHtml(), renderBodyEndHtml() защищены (непонятно от кого, особенно учитывая, что все переменные public). Как ни странно, есть повод применить антипаттерн «public morozov». Или можно просто скопипастить их в главный шаблон.
app/Yii/Web/View.php
namespace App\Yii\Web;

class View extends \yii\web\View
{
    public function getHeadHtml()
    {
        return parent::renderHeadHtml();
    }

    public function getBodyBeginHtml()
    {
        return parent::renderBodyBeginHtml();
    }

    public function getBodyEndHtml($ajaxMode = false)
    {
        return parent::renderBodyEndHtml($ajaxMode);
    }

    public function initAssets()
    {
        \yii\web\YiiAsset::register($this);

        ob_start();

        $this->beginBody();
        $this->endBody();

        ob_get_clean();
    }
}


В Yii регистрация ассетов происходит в функции endBody(), а также весь рендеринг оборачивается в буфер, в котором потом производится замена магических констант CDATA на реальные ассеты. Эмуляция этого поведения находится в функции initAssets(). Заменять мы ничего не будем, нам нужно просто чтобы были заполнены свойства $this->js, $this->css и другие.

routes/web.php
'components' => [
    ...
    'view' => [
        'class' => \App\Yii\Web\View::class,
    ],
],



resources/views/admin/order/index.blade.php


    
        ...

        
        {!! \yii\helpers\Html::csrfMetaTags() !!}
        {!! $view->getHeadHtml() !!}
    
    
        {!! $view->getBodyBeginHtml() !!}

        ...

        {!! $view->getBodyEndHtml() !!}
    



Вызов Html::csrfMetaTags() нужен, так как скрипт yii.js берет csrf-токен из HTML страницы.

ArrayDataProvider работает, но надо сделать аналог ActiveDataProvider, чтобы получать из базы только то что нужно.
app/Yii/Data/EloquentDataProvider.php
class EloquentDataProvider extends \yii\data\BaseDataProvider
{
    public $query;

    public $key;

    protected function prepareModels()
    {
        $query = clone $this->query;

        if (($pagination = $this->getPagination()) !== false) {
            $pagination->totalCount = $this->getTotalCount();
            if ($pagination->totalCount === 0) {
                return [];
            }
            $query->limit($pagination->getLimit())->offset($pagination->getOffset());
        }

        if (($sort = $this->getSort()) !== false) {
            $this->addOrderBy($query, $sort->getOrders());
        }

        return $query->get()->all();
    }

    protected function prepareKeys($models)
    {
        $keys = [];
        if ($this->key !== null) {
            foreach ($models as $model) {
                $keys[] = $model[$this->key];
            }

            return $keys;
        } else {
            $pks = $this->query->getModel()->getKeyName();

            if (is_string($pks)) {
                $pk = $pks;
                foreach ($models as $model) {
                    $keys[] = $model[$pk];
                }
            } else {
                foreach ($models as $model) {
                    $kk = [];
                    foreach ($pks as $pk) {
                        $kk[$pk] = $model[$pk];
                    }
                    $keys[] = $kk;
                }
            }

            return $keys;
        }
    }

    protected function prepareTotalCount()
    {
        $query = clone $this->query;
        $query->orders = null;
        $query->offset = null;

        return (int) $query->limit(-1)->count('*');
    }

    protected function addOrderBy($query, $orders)
    {
        foreach ($orders as $attribute => $order) {
            if ($order === SORT_ASC) {
                $query->orderBy($attribute, 'asc');
            } else {
                $query->orderBy($attribute, 'desc');
            }
        }
    }
}

app/Http/Controllers/Admin/OrderController.php
    'dataProvider' => new \App\Yii\Data\EloquentDataProvider([
        'query' => Order::query(),
        'pagination' => ['route' => $request->route()->uri(), 'defaultPageSize' => 10],
        'sort' => ['route' => $request->route()->uri(), 'attributes' => ['id']],
    ]),




Метки и фильтры.



Нужно сделать базовую модель, унаследованную от \yii\base\Model, которая будет возвращать гриду метки для колонок и правила полей для фильтрации. Для этого есть параметр filterModel. Сделаем ее конфигурируемой через конструктор.

app/Yii/Data/FilterModel.php
namespace App\Yii\Data;

use App\Yii\Data\EloquentDataProvider;
use Route;

class FilterModel extends \yii\base\Model
{
    protected $labels;
    protected $rules;
    protected $attributes;


    public function __construct($labels = [], $rules = [])
    {
        parent::__construct();

        $this->labels = $labels;
        $this->rules = $rules;

        $safeAttributes = $this->safeAttributes();
        $this->attributes = array_combine($safeAttributes, array_fill(0, count($safeAttributes), null));
    }

    public function __get($name)
    {
        if (array_key_exists($name, $this->attributes)) {
            return $this->attributes[$name];
        } else {
            return parent::__get($name);
        }
    }

    public function __set($name, $value)
    {
        if (array_key_exists($name, $this->attributes)) {
            $this->attributes[$name] = $value;
        } else {
            parent::__set($name, $value);
        }
    }

    public function rules()
    {
        return $this->rules;
    }

    public function attributeLabels()
    {
        return $this->labels;
    }

    public function initDataProvider($query, $sortAttirbutes = [], $route = null)
    {
        if ($route === null) { $route = Route::getCurrentRoute()->uri(); }
        $dataProvider = new EloquentDataProvider([
            'query' => $query,
            'pagination' => ['route' => $route],
            'sort' => ['route' => $route, 'attributes' => $sortAttirbutes],
        ]);

        return $dataProvider;
    }

    public function applyFilter($params)
    {
        $query = null;

        $dataProvider = $this->initDataProvider($query);

        return $dataProvider;
    }
}



Можно унаследоваться и определить специализированную модель, и поместить все туда.
namespace App\Forms\Admin;

use App\Yii\Data\FilterModel;

class OrderFilter extends FilterModel
{
    public function rules()
    {
        return [
            ['id', 'safe'],
            ['user.name', 'safe'],
        ];
    }

    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'created_at' => 'Created At',
            'updated_at' => 'Updated At',
            'user.name' => 'User',
        ];
    }

    public function applyFilter($params)
    {
        $this->load($params);

        $query = \App\Models\Order::query();
        $query->join('users', 'users.id', '=', 'orders.user_id')->select('orders.*');

        if ($this->id) $query->where('orders.id', '=', $this->id);
        if ($this->{'user.name'}) $query->where('users.name', 'like', '%'.$this->{'user.name'}.'%');

        $sortAttributes = [
            'id',
            'user.name' => ['asc' => ['users.name' => SORT_ASC], 'desc' => ['users.name' => SORT_DESC]],
        ];

        $dataProvider = $this->initDataProvider($query, $sortAttributes);
        $dataProvider->pagination->defaultPageSize = 10;

        if (empty($dataProvider->sort->getAttributeOrders())) {
            $dataProvider->query->orderBy('orders.id', 'asc');
        }

        return $dataProvider;
    }
}

app/Http/Controllers/Admin/OrderController.php
public function index(Request $request)
{
    $filterModel = new \App\Forms\Admin\OrderFilter();

    $dataProvider = $filterModel->applyFilter($request);

    $gridViewConfig = [
        'dataProvider' => $dataProvider,
        'filterModel' => $filterModel,
        ...
    ];
    ...
}



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


Просмотр


Тут делаем аналогично, настройки колонок можно скопировать из грида. Товары в заказе сделаем отдельным гридом на странице просмотра. Метки тоже пока оставим автогенерируемые.

app/Http/Controllers/Admin/OrderController.php
public function view($id)
{
    $model = Order::findOrFail($id);

    $detailViewConfig = [
        'model' => $model,
        'attributes' => [
            'id',
            'user.name',
            'created_at:datetime',
            'updated_at:datetime',
        ],
    ];

    $gridViewConfig = [
        'dataProvider' => new \App\Yii\Data\EloquentDataProvider([
            'query' => $model->items(),
            'pagination' => false,
            'sort' => false,
        ]),
        'layout' => '{items}{summary}',
        'columns' => [
            'id',
            'product.name',
            'created_at:datetime',
            'updated_at:datetime',
        ],
    ];

    return view('admin.order.view', ['model' => $model, 'detailViewConfig' => $detailViewConfig, 'gridViewConfig' => $gridViewConfig]);
}

resources/views/admin/order/view.blade.php
@extends('layouts.main')

@section('title', 'Index')

@section('content')

    

Order: {{ $model->id }}

Update Delete

{!! \yii\widgets\DetailView::widget($detailViewConfig) !!}

Order Items

{!! \yii\grid\GridView::widget($gridViewConfig) !!} @endsection




Создание / Обновление



Сначала нужно сделать модель формы, враппер для Eloquent моделей, унаследованный от \yii\base\Model, чтобы компонент ActiveForm мог вызывать нужные методы.

app/Yii/Data/FormModel.php
namespace App\Yii\Data;

use Illuminate\Database\Eloquent\Model as EloquentModel;

class FormModel extends \yii\base\Model
{
    protected $model;
    protected $labels;
    protected $rules;
    protected $attributes;

    public function __construct(EloquentModel $model, $labels = [], $rules = [])
    {
        parent::__construct();

        $this->model = $model;
        $this->labels = $labels;
        $this->rules = $rules;

        $fillable = $model->getFillable();
        $attributes = [];
        foreach ($fillable as $field) {
            $attributes[$field] = $model->$field;
        }

        $this->attributes = $attributes;
    }

    public function getModel()
    {
        return $model;
    }

    public function __get($name)
    {
        if (array_key_exists($name, $this->attributes)) {
            return $this->attributes[$name];
        } else {
            return $this->model->{$name};
        }
    }

    public function __set($name, $value)
    {
        if (array_key_exists($name, $this->attributes)) {
            $this->attributes[$name] = $value;
        } else {
            $this->model->{$name} = $value;
        }
    }

    public function rules()
    {
        return $this->rules;
    }

    public function attributeLabels()
    {
        return $this->labels;
    }

    public function save()
    {
        if (!$this->validate()) {
            return false;
        }

        $this->model->fill($this->attributes);
        return $this->model->save();
    }
}



Теперь можно сделать редактирование.

app/Http/Controllers/Admin/OrderController.php
    public function create(Request $request)
    {
        $model = new Order();
        $formModel = new \App\Yii\Data\FormModel(
            $model,
            ['user_id' => 'User'],
            [['user_id', 'safe']]
        );

        if ($request->isMethod('post')) {
            if ($formModel->load($request->input()) && $formModel->save()) {
                return redirect()->route('admin.order.view', ['id' => $model->id]);
            }
        }

        return view('admin.order.create', ['formModel' => $formModel]);
    }

    public function update($id, Request $request)
    {
        $model = Order::findOrFail($id);
        $formModel = new \App\Yii\Data\FormModel(
            $model,
            ['user_id' => 'User'],
            [['user_id', 'safe']]
        );

        if ($request->isMethod('post')) {
            if ($formModel->load($request->input()) && $formModel->save()) {
                return redirect()->route('admin.order.view', ['id' => $model->id]);
            }
        }

        return view('admin.order.update', ['formModel' => $formModel]);
    }

resources/views/admin/order/_form.blade.php


    {!! $form->field($formModel, 'user_id')->dropDownList(\App\User::pluck('name', 'id'), ['prompt' => '']) !!}

    




Правила валидации задаются в стиле Yii. Если нужно, можно переопределить метод validate() и вызывать там валидатор Laravel. В данном примере мы этого делать не будем.

Blade не разрешает объявлять переменные. А ActiveForm::begin() и выводит теги и возвращает значение. Можно явно написать тег , можно сделать новый тег через Blade::extend(), как советуют здесь, можно сделать обертку для ActiveForm. Пока оставим .

Как и в случае с фильтром, можно унаследоваться от FormModel и поместить все объявления туда.

app/Forms/Admin/OrderForm.php
namespace App\Forms\Admin;

class OrderForm extends FormModel
{
    public function rules()
    {
        return [
            ['user_id', 'safe'],
        ];
    }

    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'user_id' => 'User',
            'created_at' => 'Created At',
            'updated_at' => 'Updated At',
            'user.name' => 'User',
        ];
    }
}




Метки на странице просмотра


Теперь можно использовать OrderForm, чтобы задать метки в методе app/Http/Controllers/Admin/OrderController.php.
$formModel = new \App\Forms\Admin\OrderForm($model);

$detailViewConfig = [
    'model' => $formModel,
    ...
];



Удаление


Тут все просто.
app/Http/Controllers/Admin/OrderController.php
public function delete($id)
{
    $model = Order::findOrFail($id);
    $model->delete();

    return redirect()->route('admin.order.index');
}



Дополнения


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

composer require yiisoft/yii2-gii --dev

routes/web.php
    $config = [
        'components' => [
            ...
            'db' => [
                'class' => \yii\db\Connection::class,
                'dsn' => 'mysql:host='.env('DB_HOST', 'localhost')
                    .';port='.env('DB_PORT', '3306')
                    .';dbname='.env('DB_DATABASE', 'forge'),
                'username' => env('DB_USERNAME', 'forge'),
                'password' => env('DB_PASSWORD', ''),
                'charset' => 'utf8',
            ],
            ...
        ],
    ];

    if (YII_DEBUG) {
        $config['modules']['gii'] = ['class' => \yii\gii\Module::class];
        $config['bootstrap'][] = 'gii';
    }

    (new \yii\web\Application($config));  // initialization is in constructor
    Yii::setAlias('@bower', Yii::getAlias('@vendor') . DIRECTORY_SEPARATOR . 'bower-asset');
    Yii::setAlias('@App', Yii::getAlias('@app') . DIRECTORY_SEPARATOR . 'App');

    ...

    Route::any('gii{params?}', function () {
        $request = \Yii::$app->getRequest();
        $request->setBaseUrl('/admin');
        \Yii::$app->run();
        return null;
    })->where('params', '(.*)');


Yii::setAlias('@App') — путь к файлам определяется через Yii::getAlias('@'...), поэтому для класса App\Models\Order будет проверяться путь '@App/Models/Order.php'.
setBaseUrl('/admin') — нужно, чтобы роутинг Yii обрабатывал только часть после '/admin'.

С Yii::setAlias('@App') и ['Yii', 'autoload'] есть такая проблема. Если не отключить автозагрузчик, то при неправильном названиии класса или неймспейса в существующем файле происходит ошибка, которая неправильно обрабатывается. Происходит это так. Он подключает файл, но потом не находит класс и бросает исключение UnknownClassException. Вызывается автозагрузчик Laravel, который проверяет фасады и алиасы и тоже ничего не находит. Потом вызывается автозагрузчик Composer, который снова подключает файл, и возникает уже другая ошибка 'Cannot declare class '...', because the name is already in use'. Приложение падает с ошибкой 500 без записи в лог.

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

vendor\yiisoft\yii2-gii\Module.php
protected function resetGlobalSettings()
{
    if (Yii::$app instanceof \yii\web\Application) {
        Yii::$app->assetManager->bundles = [];
    }
}



Можно вынести конфигурацию ActionColumn в отдельный класс, чтобы не копировать в разные гриды.

app/Yii/Widgets/ActionColumn.php
namespace App\Yii\Widgets;

use URL;
use Route;

class ActionColumn extends \yii\grid\ActionColumn
{
    public $keyAttribute = 'id';
    public $baseRoute = null;
    public $separator = '.';

    /**
     * Overrides URL generation to use Laravel routing system
     *
     * @inheritdoc
     */
    public function createUrl($action, $model, $key, $index)
    {
        if (is_callable($this->urlCreator)) {
            return call_user_func($this->urlCreator, $action, $model, $key, $index, $this);
        } else {
            if ($this->baseRoute === null) {
                $this->baseRoute = Route::getCurrentRoute()->getName();
            }

            $baseRouteParts = explode($this->separator, $this->baseRoute);
            $baseRouteParts[count($baseRouteParts) - 1] = $action;
            $route = implode($this->separator, $baseRouteParts);

            $params = is_array($key) ? $key : [$this->keyAttribute => (string) $key];

            return URL::route($route, $params, false);
        }
    }
}



Можно сделать обертку для ActiveForm, куда поместить вызов виждета, и передавать модель в конструктор. Это позволит убрать прямые теги и передачу модели в каждое поле. Также туда можно добавлять дополнительные методы для инициализации сторонних виджетов полей типа Select2.

app/Yii/Widgets/FormBuilder.php
namespace App\Yii\Widgets;

use yii\widgets\ActiveForm;
use yii\helpers\Html;

class FormBuilder extends \yii\base\Component
{
    protected $model;
    protected $form;


    public function __construct($model)
    {
        $this->model = $model;
    }

    public function getModel()
    {
        return $this->model;
    }

    public function setModel($model)
    {
        $this->model = $model;
    }

    public function getForm()
    {
        return $this->form;
    }

    public function open($params = ['successCssClass' => ''])
    {
        $this->form = ActiveForm::begin($params);
    }

    public function close()
    {
        ActiveForm::end();
    }

    public function field($attribute, $options = [])
    {
        return $this->form->field($this->model, $attribute, $options);
    }

    public function submitButton($content, $options = ['class' => 'btn btn-primary'])
    {
        return Html::submitButton($content, $options);
    }
}



resources/views/admin/order/_form.blade.php
{!! $form->open() !!}

    {!! $form->field('user_id')->dropDownList(
        \App\User::pluck('name', 'id'),
        ['prompt' => ''])
    !!}

    {!! $form->submitButton('Submit'); !!}

{!! $form->close() !!}




Исходный код


Исходный код можно найти здесь. Все шаги сделаны отдельными коммитами. Есть миграции и тестовые данные.
php artisan migrate:refresh --seed


Обертки находятся в папке app/Yii.
Обязательные:
App\Yii\Web\Request
App\Yii\Data\EloquentDataProvider
App\Yii\Data\FormModel

Без остальных можно обойтись, но с ними удобнее:
App\Yii\Data\FilterModel
App\Yii\Web\View
App\Yii\Widgets\ActionColumn
App\Yii\Widgets\FormBuilder


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

https://habrahabr.ru/post/334542/


Метки:  

Знакомство с Университетом ИТМО: дайджест практических работ

Понедельник, 31 Июля 2017 г. 15:48 + в цитатник
Сегодня мы решили подготовить тематический дайджест, который позволяет познакомиться с проектами сообщества Университета ИТМО. Здесь мы расскажем о разработке новых систем связи, робототехнике, семантических технологиях и многом другом.



(^22) Квантовые коммуникации: от НИР до технологического бизнеса
Линия связи, которую невозможно «прослушать» — защита за счет законов физики и принципов квантовой криптографии. Реализовать такую концепцию удалось сотрудникам одного из малых инновационных предприятий при Университете ИТМО. В этом материале мы рассказываем о том, как начинался данный проект, что лежит в его основе и как устроена его работа.

(^10) Разработка невзламываемых систем хранения данных
Продолжение «истории» со взломоустойчивыми технологиями. На этот раз основная задача заключается во внедрении квантовых систем защиты в работу распределенных ЦОД.

(^10) Лазеры для систем наведения и геодезии: Инновации от Университета ИТМО
Рассказываем о принципах работы и возможностях для применения новых проектов. От импульсных источников света для дальномеров до компактного лазера для беспилотников.

(^13) Как студенты Университета ИТМО создают роботов
Знакомим вас со Студенческим конструкторским бюро (Robotics engineering department или RED), историей проекта «RED», ходом работ и используемыми технологиями. Плюс говорим о практических наработках: роботе-промоутере и многоагентной системе на базе Robotino.

(^8) Роботы и робототехника: Дайджест Университета ИТМО
Наша тематическая подборка последних исследований и разработок в сфере робототехники. Здесь собраны публикации в журналах Университета ИТМО по самым разным направлениям: от стабилизации двуногих роботов до внедрения технологии дополненной реальности в пилотируемые космические комплексы.

/ Фото Rama V / CC

(^12) Как мы помогаем организаторам Кубка конфедераций и ЧМ-2018
Ученые Университета ИТМО работают над безопасностью спортивных мероприятий — занимаются моделированием поведения толпы в рамках проектов Института наукоемких компьютерных технологий (НИИ НКТ). Здесь мы рассказываем о том, почему это важно, приводим примеры проектов и методы сбора информации для анализа.

(^20) «Моделируем будущее»: от предсказания поведения толпы до анализа мнений
Продолжение знакомства с проектами НИИ НКТ. В этом материале мы рассказываем о дополнительных возможностях и сферах применения алгоритмов, разработанных учеными.

(^14) «Познай самого себя»: social media mining-проекты в Университете ИТМО
Социальные сквозняки, распространение информации, профилирование в соцсетях и анализ публикаций в Facebook и Instagram, плюс разбор Twitter-профиля Дональда Трампа. Обо всех этих исследованиях и направлениях для дальнейшей работы — в нашем хабрапосте.

(^10) Как можно применять «большие данные» в страховании
Аналитические компании предсказывают рост объема рынка специализированного ПО, работающего с Big Data. А мы — рассказываем о том, какие аналитические задачи на стыке сфер страхования и «больших данных» решают проекты Университета ИТМО.

(^9) Дайджест: бизнес-проекты, инициативы и советы предпринимателям
Университет ИТМО заинтересован в развитии инновационного бизнеса. На нашей площадке проходят десятки мероприятий: от тематических семинаров до хакатонов. Помимо этого мы занимаемся активной работой с международным сообществом и поддерживаем студенческие инициативы. Обо всем этом — наш тематический дайджест.

/ Фотография Университета ИТМО

(^16) «Нейротеатр», построенный с помощью технологий Университета ИТМО
Рассказываем о премьере экспериментального проекта в жанре «нейротеатр», которая состоялась на фестивале Geek Picnic в Санкт-Петербурге. Кто все это сделал и как это работает – об этом в нашем материале.

(^9) Управление «силой мысли»: резидент нашего акселератора
Продолжая тему, говорим о нейроинтерфейсах, специальном приложении и тематическом проекте TuSion. Он является резидентом акселератора Future Technologies Университета ИТМО.

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

(^16) Разработки Университета ИТМО: Управление дронами на основе блокчейн
Как работает «Дрон-сотрудник», построенный на базе платформы Ethereum. Рассказ о том, почему проект «опережает время» и немного о том, кто еще в Университете ИТМО исследует эти сферы.

(^24) Как технологии меняют качество жизни: разработка «умных» протезов
Как и для кого выпускники Университета ИТМО разрабатывают протезы и даже экзоскелеты. Что можно предложить для улучшения качества реабилитации, как помочь слепым и слабовидящим людям ориентироваться в пространстве — ответы на все эти вопросы в нашем материале.

(^16) Новая система передачи энергии на расстоянии
Один из многочисленных примеров разработок ученых из Университета ИТМО. На этот раз речь идет о беспроводной передаче энергии. Тесты показывают, что эффективность технологии достигает 90%. В этой статье мы разбираемся с перспективами развития проекта.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334522/


Метки:  

Обзор Lokalise — сервиса для локализации приложений и обновления переводов «по воздуху»

Понедельник, 31 Июля 2017 г. 15:43 + в цитатник
Главный редактор популярного сайта для разработчиков и IT-специалистов TProger.ru, выросшего из ВК-паблика «Типичный программист», сделал весьма толковый обзор Lokalise, которым мы с вами с удовольствием делимся. Кто работает с локализацией мобильных и веб-приложений — те особенно оценят.

Lokalise — сервис для автоматизации процесса локализации приложений, который выбрали уже более 1 000 команд, включая MSQRD, Carousell, MEGOGO, Depositphotos. Мы изучили возможности платформы и в этом обзоре расскажем, за счет чего Lokalise помогает провести локализацию быстро и просто.

image

Первое знакомство


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


image
Первая подсказка

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

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

Работа с локализацией


Сервис поддерживает множество различных форматов файлов: от наиболее популярных Apple Strings (.strings), Android Resources (.xml) и Comma-separated values (.csv) до JSON (.json) и книг Excel (.xslx). Необходимое количество ключей (меток) можно также создать и дублировать вручную без загрузки файла.

В рамках одного проекта вы можете настроить перевод сразу на несколько языков. Для каждой метки есть несколько вариантов:

  • машинный перевод от Яндекс.Переводчика;
  • машинный перевод от Google.Translate;
  • подсказка из translation memory — сервис подскажет вам наиболее подходящий перевод на основе уже переведенных меток;
  • также вы можете оставить слово без перевода — это удобно тогда, когда термин в переводе не нуждается.

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

image
Панель с настройками перевода, справа под надписью «TO ORDER» отображается цена работы

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

В результате вы получите готовый файл с локализациями. Для скачивания можно выбрать удобный вам формат из множества доступных — в частности, с помощью Lokalise можно локализовать iOS-, Android- и веб-приложения. Сервис поддерживает интеграцию с App Store и Google Play для импорта и экпорта метаданных.

Командная работа над переводом


Lokalise подходит для работы над локализацией в команде.

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

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

Также доступна история работы над переводом каждой отдельной метки:

image
История переводов

Сервис поддерживает интеграцию со Slack и может присылать вам уведомления об изменениях в проекте при каждом сохранении. Также вы получите уведомления на e-mail и/или в Slack (в зависимости от настроек), если будете упомянуты в комментарии через «@». Еще одна полезная особенность — возможность участников работы над локализацией голосовать за различные варианты перевода.

API и SDK сервиса


Помимо «ручного» режима, Lokalise поддерживает также работу через хорошо документированный API по HTTPS. Доступ к API сервиса осуществляется по токену. Данные возвращаются в формате JSON.

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

Доступные тарифные планы


Сервис имеет несколько тарифных планов: бесплатный, «Startup» за $40 в месяц при оплате за год или $50 случае помесячной оплаты, «Business» за $80 в месяц (соответственно $100). Все они отличаются ограничениями на число ключей, проектов, одновременно участвующих в разработке сотрудников и наличием доступа к SDK под iOS и Android (не доступен в рамках бесплатного плана) и прочим возможностям.

Также существует безлимитное предложение «Enterprise», которое оформляется в индивидуальном порядке, а также предоставляется бесплатно для open source проектов. Помимо отсутствия ограничений, этот план включает поддержку персональным менеджером и возможность заказать на доработку необходимую функциональность. При регистрации вы бесплатно получаете 14-дневный тестовый период с «Enterprise»-планом.

Мы также пообщались с коммерческим директором Lokalise — Петром Антроповым. Петр ответил нам на несколько интересных вопросов.

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

Бесплатный план довольно сильно обрезан. Он для тех, у кого нет $40 ($50) на нормальный план. Обычно те команды, что идут на новые рынки, достаточно хорошо профинансированы, поэтому бесплатный план выбирают либо не самые уверенные стартапы, либо принципиальные пользователи бесплатных решений, а их становится всё меньше и меньше. В бесплатных планах только 3 пользователя, 1000 меток (ключей), нет мобильных SDK, нет translation memory (автоматические предложения перевода на основе прошлых подобных) и translation history. Что-то можно делать, но и только.

Есть ли какие-нибудь бонусы для клиентов, сотрудничающих с вами длительное время?

Большинство команд покупают годовую, а не месячную подписку, она на 20% дешевле. В остальном, по клиентским отзывам, наш сервис наиболее удобен в использовании и богат на «фичи». К нам переходят из других сервисов, и пока что мы не видели ни одной отписки. Команды разработчиков, стартапы, веб-, мобильные студии и крупные компании готовы платить за сервисы, которые ускоряют выход на новые рынки, облегчают работу, убирают рутину, снижают количество багов и предоставляют уникальный функционал, c которым они делают то, что раньше не могли.

По поводу open source: как именно вы работаете с такими проектами? Какие необходимы подтверждения или т.п., чтобы получить по этой программе бесплатный «Enterprise» план?

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

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

Помимо самого удобного веб-интерфейса (мультиязычный вид и т.п.) — это наличие iOS и Android SDK для мгновенного обновления текстов без новых версий в магазинах. С этим функционалом обновлять тексты можно так часто, как это необходимо, без долбежки пользователей обновлениями. Обновление происходит мгновенно, это не 1-2 дня одобрения обновления в App Store. Плюс это гарантия, что 100% пользователей сразу увидят новые тексты, а не 80% пользователей обновят приложение к исходу второго месяца, как это обычно происходит.

Также это мультиплатформенность меток (ключей), универсальные плейсхолдеры, поиск дубликатов и голосование по переводам. По словам пользователей, у нас лучший на рынке API и утилита CLI. Мы поддерживаем plurals (форму слова во множественном числе — прим. ред.), у нас есть референсы между ключами. Другими словами, Lokalise — это наиболее продвинутое решение по локализации и автоматизации переводов.

Над какими «фичами» ведется работа сейчас? Что можно ожидать в близкой перспективе, а что в длительной?

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

Далее будет инструмент по «заворачиванию» захардкоденного текста в метки (ключи), без которого этот процесс происходит вручную у тех, кто на скорую руку верстает, а потом думает про перевод.

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



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

Резюмируя, делимся мнением Tproger: Lokalise существенно упрощает и ускоряет работу с локализациями, в том числе командную — в частности, вы можете быстро поделиться результатами с партнером, чтобы уточнить варианты для локализации.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334528/


GeekUniversity открывает набор студентов на факультет Python-разработки

Понедельник, 31 Июля 2017 г. 14:34 + в цитатник


В нашем онлайн-университете для программистов открылся новый факультет. Теперь в GeekUniversity студенты смогут освоить Python-разработку на Middle-уровне и гарантированно начать карьеру сразу после обучения.


GeekUniversity — совместный образовательный проект Mail.Ru Group и IT-портала GeekBrains. Программу обучения и спецкурсы для факультета разрабатывают Avito, Альфа-банк, МТС, Тинькофф, DeliveryClub.


За один год студенты создают 2 самостоятельных и 2 командных проекта. Прогресс будет курировать наставник. После обучения выпускники умеют:


  • верстать сайты на HTML, CSS, Bootstrap;
  • создавать сайты на Django framework;
  • работать в команде по методологиям разработки Agile и Scrum;
  • работать с GIT;
  • применять принципы ООП в работе;
  • использовать шаблоны проектирования singletone, adapter, factory, dependency injection;
  • успешно проходить собеседования и общаться с заказчиками.

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


Оставить заявку и получить подробную консультацию можно здесь.

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

https://habrahabr.ru/post/334532/


Метки:  

Поиск сообщений в rss_rss_hh_new
Страницы: 1437 ... 1073 1072 [1071] 1070 1069 ..
.. 1 Календарь