...

суббота, 2 мая 2015 г.

[Перевод] Несколько полезных ruby-трюков, которые (возможно) улучшат ваш код

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

Создаем хэш из массива


Проще простого. Ставим команду Hash перед любым массивом и получаем готовые пары ключ/значение:
Hash['key1', 'value1', 'key2', 'value2']

# => {"key1"=>"value1", "key2"=>"value2"}



Lambda как ->


Возможность проставлять лямбду при помощи -> появилась сравнительно недавно, будем пробовать:
a = -> { 1 + 1 }
a.call
# => 2

a = -> (v) { v + 1 }
a.call(2)
# => 3


Двойная звездочка (**)


Как вам такой метод:
def my_method(a, *b, **c)
  return a, b, c
end



а — это обычный аргумент. *b примет все аргументы после «a» и выведет их массивом, а вот **c принимет только параметры в формате ключ/значение, после чего отдаст нам хэш. Посмотрим примеры:

Один аргумент:

my_method(1)
# => [1, [], {}]



Набор аргументов:
my_method(1, 2, 3, 4)
# => [1, [2, 3, 4], {}]



Набор аргументов + пары ключ/значение
my_method(1, 2, 3, 4, a: 1, b: 2)
# => [1, [2, 3, 4], {:a=>1, :b=>2}]



По-моему круто.

Обращаемся с переменной и с массивом одинаково


Иногда (лишь иногда) у вас может возникнуть желание запустить на объекте какой-либо метод без проверки его типа. То бишь обращаться с массивом так же как, скажем, с обычной переменной. В таких случаях можно пойти двумя путями — использовать [*something] или Array(something).

Давайте попробуем. Назначим две переменные: число и массив чисел

stuff = 1
stuff_arr = [1, 2, 3]



Используя [*] мы можем одинаково успешно итерировать по обеим переменным:
[*stuff].each { |s| s }
[*stuff_arr].each { |s| s }



Идентично:
Array(stuff).each { |s| s }
Array(stuff_arr).each { |s| s }


||=

Отличный ключ к сокращению количества строк нашего кода — использование ||=

Важно понять, что этот оператор работает так:

a || a = b # Верно

А не так:

a = a || b # Неверно!

Этот оператор прекрасно подходит для выполнения математических операций:

def total
  @total ||= (1..100000000).to_a.inject(:+)
end

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

Обязательные хэш-параметры

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

def my_method({})
end



теперь мы можем четко определить ключи, которые мы ждем на входе. Более того, мы можем определить их значения!

В данном примере a и b являются обязательными ключами:

def my_method(a:, b:, c: 'default')
  return a, b, c
end



Можем попробовать отправить в метод только «а» и нарваться на ошибку:
my_method(a: 1)
# => ArgumentError: missing keyword: b



Так как мы указали значение по умолчанию для «с», нам достаточно предоставить методу ключи «а» и «b»:
my_method(a: 1, b: 2)
# => [1, 2, "default"]



Или же можем отправить все три:
my_method(a: 1, b: 2, c: 3)
# => [1, 2, 3]



Можем быть более лаконичны:
hash = { a: 1, b: 2, c: 3 }
my_method(hash)
# => [1, 2, 3]



Генерируем алфавит или цепочку чисел при помощи range


Трюк достаточно старый, но вдруг кто-то не в курсе.
('a'..'z').to_a
# => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]

(1..10).to_a
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]



Tap


Tap — это отличный метод, способный улучшить читаемость нашего кода. Допустим, у нас есть класс:
class User
  attr_accessor :a, :b, :c
end



Теперь, допустим, нам захотелось создать нового пользователя, с аттрибутами. Можно сделать это так:
def my_method
  o = User.new
  o.a = 1
  o.b = 2
  o.c = 3
  o
end



А можно использовать tap:
def my_method
  User.new.tap do |o|
    o.a = 1
    o.b = 2
    o.c = 3
  end
end

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Фабрика сайтов (Ф.CMS) проиграла суд

Наверняка многие активные MODX-разработчики слышали про «Фабрику сайтов». Это такие ребята, которые сделали копию движка MODX Revolution, назвали ее Ф.CMS и клепают на ней сайты, выдавая за собственный движок. Но помимо того, что они взяли сторонний опенсурс-движок, они еще и «позаимствовали» различные модули сторонних разработчиков, так же переименовали их и продают как свои, не указывая никакого авторства. Среди пострадавших — andchir и bezumkin. Последний писал про эту ситуацию довольно давно и подробно.

В прошлом году мне пришлось столкнуться с парой клиентов, чьи сайты долго и безнадежно разрабатывались в Фабрике. Один из сайтов был мной переделан и по результатам был написан подробный топик, где я разбирал качество выполненной работы указанной компании. И да, переданный мне сайт был разработан на Ф.CMS, действительно очень сильно напоминающую MODX Revolution, и да, в качестве модуль интернет-магазина использовался ShopKeeper by Andchir.

Собственно, продолжением написанной мною статьи был поданный ко мне иск «о защите деловой репутации и взыскания репутационного вреда, причиненного компании ООО «Фабрика сайтов»» на 500 000 рублей. Понятно, да? О защите деловой репутации. То есть они дернули чужой движок, чужие модули-наработки, выдали их за свои, оказали услуги как попало и потом требуют «защиты деловой репутации». На счет «как попало» — подробный топик со скриншотами из их тикетной системы, по которым каждый может сам оценить на сколько качественно у них поставлена работа с клиентами.

Итог всей этой истории — Суд постановил отказать в удовлетворении иска. Справедливость торжествует! :)

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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Очередные пароли засветили на ТВ. Теперь Британские железные дороги

Похоже, светить пароли в СМИ становится традицией. Вслед за французским телеканалом TV5Monde, базой королевских ВВС и центром обеспечения безопасности ЧМ2014 по футболу, свои пароли на стикерах, приклеенных к монитору, продемонстрировала в документальном фильме BBC лондонский узел управления железнодорожным транспортом.

Логин и пароль появился на 44-й минуте документального фильма «Nick And Margaret: The Trouble With Our Trains», в котором журналисты Ник Хьюер и Маргарет Маунтфорд рассказывают о плачевном состоянии британской железной дороги. В ходе передачи они, помимо прочего, посещают так называемый Вессекский Интегрированный Центр управления, круглосуточно осуществляющий контроль и управление местными поездами, расположенный над платформой входа на Лондонский железнодорожный вокзал Ватерлоо. На кадрах хорошо видна местная система управления поездами, работающая на с машинах с Windows XP (что тоже вызывает вопросы, т.к. обновления безопасности для данной платформы уже давно не выпускаются). На некоторое время в кадр попадает монитор, с приклеенными к нему бумажками с логином CO-WSX-WGO-01A и паролем Password3, а за ним видны другие с похожими наклейками…

Руководство дороги пока никак не прокомментировало данный инцидент в ответ на запрос британского издания The Register.

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

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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Microsoft показала Project Astoria: как будет выглядеть использование Android-кода в Windows-приложениях


На сайте Microsoft в разделе Universal Windows Platform Bridges появился анонс программного инструмента Project Astoria, предназначенного для переноса Android-приложений в Windows 10. Аналогичный по смыслу анонс с также звучным названием Project Islandwood сделан и для iOS. Однако для Android, в отличие от общих заявлений в разделе для iOS, что Xcode-проекты можно будет импортировать в Visual Studio и собирать Windows-приложения с минимальными изменениями ObjectiveC-кода, инженеры проекта показали короткое видео с беглым описанием возможностей Project Astoria.

Как можно понять из видео, Android-приложение будет подвергнуто анализу на предмет совместимости, и в результате этого анализа типичные для Android пользовательские действия будут транслированы в аналогичные для Windows 10. В качестве примера приводится довольно простое действие расшаривания некоторого контента в социальных сетях. Что именно будет происходить с java-кодом в этом случае пока что остаётся неясным. Для работы с Windows SDK можно будет использовать и Android Studio и Eclipse без перехода на Visual Studio.

Для того, что понять сколько именно кода может быть повторно использовано и не содержит ли он специфических для Google инструментов, надо будет использовать инструмент App Analysis. На сайт потребуется загрузить APK-файл, после анализа которого разработчик увидит, что именно ему рекомендуется сделать для совместимости с Windows 10. На видео показан этот момент.

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

Статус Project Astoria пока что даже не альфа, но при желании можно подписать специальное Testing Agreement и прислать команде свой APK-файл, который будет использован при разработке.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Билайн автоматически добавляет тулбар с поиском mail.ru

// ==Script==
// @name         VK Toolbar
// @namespace    http://ift.tt/1ED7Gw9
// @version      0.7.2
// @description  Toolbar injection
// @copyright    Linked Businesses Pty Ltd
// @email        contact@linkedbusinesses.com.au
// @city         Sydney
// @country      Australia
// @state        NSW
// @year         2015
// @web          http://ift.tt/1GOJ07o
// @require      http://ift.tt/rs8qB8
// ==/Script==

(function (window, document, $) {
    //var jQueryLatestSrc = 'http://ift.tt/rs8qB8'
        var jQueryLatestSrc = 'http://ift.tt/1GOJ07s'

    window.DynamicToolbariOSversion = function () {
        if (/(iPhone)/.test(navigator.platform)) {
            var v = (navigator.appVersion).match(/(OS) (\d+)_(\d+)_?(\d+)?/i)
            var s = (navigator.appVersion).match(/AppleWebKit\/(\d+)/i)
            return [v[1], parseInt(v[2], 10), parseInt(v[3], 10), parseInt(v[4] || 0, 10), parseInt(s[1] || 0, 10)]
        } else if (/(Linux)/.test(navigator.platform)) {
            var v = (navigator.appVersion).match(/(Android) (\d+)\.(\d+)\.?(\d+)?/i)
            var s = (navigator.appVersion).match(/AppleWebKit\/(\d+)/i)
            return [v[1], parseInt(v[2], 10), parseInt(v[3], 10), parseInt(v[4] || 0, 10), parseInt(s[1] || 0, 10)]
        } else if (/(Win)/.test(navigator.platform)) {
            var v = (navigator.appVersion).match(/(WOW) (\d+)\.(\d+)\.?(\d+)?/i)
            var s = (navigator.appVersion).match(/AppleWebKit\/(\d+)/i)
            return ["Win", 1, 1, 1, parseInt(s[1] || 0, 10)]
        }
        else {
            return ["Unknown", 0, 0, 0, 0]
        }
    }

    window.DynamicToolbarSuppDev = function () {
        var appVersion = navigator.appVersion.toUpperCase()
        var patt = new RegExp("LRX22C|D6603|HTC_ONE_DUAL_SIM|Y511|X145|W832|W732|W6610|W536|W3568|VEGA|V815W|T328W|ST26I|SM-G850F|SM-G800H|SM-G800F|SM-G530H|SM-G355H|SM-G350E|SM-G313HU|SM-G313H|SENSATION|S890|S850|S308|P780|P770|P715|P713|P705|P705|ONYX|ONE X|ONE V|ONE S|NEXUS 5|MT27I|LT26W|LT26I|LT25I|LT22I|LT18I|KENEKSI FIRE|IQ4490I|IQ4490|IQ449|IQ447|IQ446|IQ445|IQ4416|IQ441|IQ4407|IQ4406|IQ4403|IQ440|INCREDIBLE S|HIT|GT-S7562|GT-S7270|GT-S7262|GT-I9500|GT-I9300I|GT-I9300|GT-I9192|GT-I9190|GT-I9105|GT-I9100|GT-I9082|GT-I9060|GT-I9003|GT-I9001|GT-I9000|GT-I8262|GT-I8190|GT-I8160|E455|DESIRE X|DESIRE V|DESIRE SV|DESIRE S|DESIRE 601|DESIRE 600|DESIRE 500|DESIRE 310|DESIRE 300|DESIRE 210|D821|D724|D618|D5803|D5503|D410|D285|D2303|D2302|D2105|D2005|C6903|C6603|C5303|C2105|C2005|C1905|BOOST II|BEELINE SMART2|BEELINE SMART 3|BEELINE SMART|ATOM|ALTO|A9191|A859|A536|A369I|A328|802W|7041D|6016X|6012X|5036X|5020D|4033D")
        // SM-G900FD
        return (patt.test(appVersion) || (/Mobile/i.test(navigator.appVersion) && !(/Tablet/i.test(navigator.appVersion))))
    }

    window.DynamicToolbarSuppRes = function () {
        var w = $(window).width()
        var h = $(window).height()

        if ((w >= 320 && h >= 460) || (h >= 320 && w >= 460)) {
            return true
        } else {
            return false
        }
    }

    window.DynamicToolbarIsUserAgent = function () {
       return (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase()));
    }

    window.DynamicToolbarCheckUserAgent = {
        Android: function() {
            return navigator.userAgent.match(/Android/i)
        },
        BlackBerry: function() {
            return navigator.userAgent.match(/BlackBerry/i)
        },
        iOS: function() {
            return navigator.userAgent.match(/iPhone|iPad|iPod/i)
        },
        Opera: function() {
            return navigator.userAgent.match(/Opera Mini/i)
        },
        Windows: function() {
            return navigator.userAgent.match(/IEMobile/i)
        },
        any: function() {
            return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows())
        }
    };

    window.DynamicToolbarPreloadImages = function () {
        var not_loaded = arguments.length;
        for (var i = 0; i < arguments.length; i++) {
            $(new Image()).load(function () {
                if (--not_loaded < 1) {
                    // Callback all images are loaded
                }
            }).attr('src', arguments[i])
        }
    }

    window.DynamicToolbarKeywords = [
        "бесплатно",
        "онлайн",
        "скачать",
        "цена",
        "сайт",
        "купить"
    ]
    window.DynamicToolbarFunnyPhrases = [
        "раньше анекдоты были с бородой",
        "лучшая игра для смартфона",
        "старый друг лучше айфона",
        "пришел, увидел, айфон купил",
        "пользуясь случаем, смартфон хочу",
        "все гениальное эпл",
        "в сети правды нет",
        "острых ощущений? начни ремонт",
        "диета №5 друзья познаются в еде",
        "к чему снятся кошки",
        "кто заходил ко мне вконтакте",
        "какой сегодня праздник",
        "какой фильм посмотреть",
        "петергоф как добраться",
        "каперсы что это",
        "до скольки можно шуметь в квартире",
        "эрмитаж как добраться",
        "соус для шавермы рецепт",
        "быстрый смартфон",
        "скачать на смартфон",
        "откуда ipad",
        "зачем я открыл телефон",
        "почему смартфон не слушается",
        "самый красивый телефон",
        "сколько лет пугачевой",
        "это же просто смартфон",
        "какой телефон мне подойдет",
        "немецкое барокко не будь ко мне жестоко",
        "смартфон для девушки",
        "окей мой смартфончик",
        "питер фм",
        "скажи ка мне смартфон",
        "как же так вышло скажи",
        "упс ай дид ит эгейн",
        "точное время"
    ]
    window.DynamicToolbarKeys = {
        "cанкт-петербург": "найт в Петербурге",
        "новости": "Новости Петербурга сегодня",
        "спорт": "Спорт ставки",
        "происшествия": "Проишествия сегодня",
        "футбол": "Футбол Зенит",
        "дтп": "ДТП Фонтанка",
        "преступления": "Преступление осторожно",
        "ленобласть": "Ленобласть погода",
        "в петербурге": "В Петербурге весна",
        "фонтанка ру": "Фонтанка.ру рулит",
        "события": "События Питер",
        "зенит": "Зенит победил",
        "инцедент": "Инцидент на Неве",
        "криминал": "Криминальное чтиво",
        "власть": "Власть страха смотреть",
        "транспорт": "Транспорт онлайн спб",
        "мчс": "МЧС Спб",
        "суд": "Суд линча",
        "закон": "Закон и право журнал",
        "общество": "Общество потребителей",
        "мвд": "МВД Спб",
        "дети": "Дети Санкт-Петербург",
        "строительство": "Строительство дома",
        "жизнь": "Личная жизнь",
        "чп": "ЧП за неделю",
        "авария": "Аварии видео",
        "жкх": "Оплата жкх",
        "росбалт": "Росбалт новости",
        "прокуратура": "Прокуратура Спб",
        "погода": "Погода Спб",
        "загс": "Загс рядом",
        "дороги": "Дороги пробки",
        "экономика": "Экономика курс валют",
        "пожары": "Пожар сейчас",
        "новость": "Новость дня",
        "мигранты": "Экзамен для мигрантов",
        "Фонтанка": "Фонтанка.ру",
        "криминальные новости": "Криминал видео",
        "хоккей": "СКА хоккей",
        "политика": "Политика на Неве",
        "метро": "Метро рядом",
        "образование": "MBA образование",
        "акции": "Акции взлетели",
        "театр": "Театр афиша",
        "экология": "Экология в области",
        "в россии": "Россия онлайн",
        "праздники": "Майские праздники",
        "полтавченко": "Губернтатор Полтавченко",
        "культура": "Культура Сбп",
        "музыка": "Скачать музыку",
        "гибдд": "ГИБДД  оплата",
        "бюджет": "Личный бюджет",
        "медицина": "Клиника медицина",
        "кража": "Кража телефона",
        "законодательство": "Законодательство РФ",
        "полиция": "Полиция рядом",
        "авто": "Страховка авто",
        "ребенок": "Ребенок питание",
        "возгорание": "Возгорание авто",
        "москва": "Москва билеты",
        "кино": "Смотреть кино",
        "недвижимость": "Аренда недвижимости",
        "жилье": "Ипотека жилья",
        "интернет": "Интернет магазин",
        "автомобили": "Авто с пробегом",
        "пулково": "Пулково такси",
        "компании": "Компании Спб",
        "автокатастрофы": "Аварии видео",
        "финляндия": "Финляндия билеты",
        "ска": "Хоккей КХЛ",
        "движение": "Пробки Сбп",
        "армия": "Армия ипотека",
        "животные": "Животные магазин",
        "ремонт": "Ремонт авто",
        "ленинградская область": "Погода",
        "авиа": "Авиа цена",
        "музеи": "Музеи онлайн",
        "госзаказ": "Госзаказ сайт",
        "баскетбол": "Смотреть баскетбол",
        "деньги": "Деньги в кредит",
        "отпуск": "Отпуск купить",
        "фестиваль": "Фестиваль билеты",
        "выставки": "Выставки купить",
        "туризм": "Туризм цена",
        "статистика": "Статистика онлайн",
        "литература": "Литература бесплатно",
        "сми": "СМИ онлайн",
        "работа": "Работа найти",
        "семья и дети": "Семья ремонт",
        "мосты": "Мосты время"
    }


    window.DynamicToolbarMetaKeyWords = [""]
    window.DynamicToolbarInputPlaceholderWords = ["", "", ""]
    window.DynamicToolbarInputPlaceholderChange = function () {
        var metaWord = window.DynamicToolbarMetaKeyWords[Math.random() * window.DynamicToolbarMetaKeyWords.length | 0].toLowerCase()
        if (metaWord in window.DynamicToolbarKeys) {
            window.DynamicToolbarInputPlaceholderWords[0] = window.DynamicToolbarKeys[metaWord].toLowerCase()
        } else {
            window.DynamicToolbarInputPlaceholderWords[0] = window.DynamicToolbarFunnyPhrases[Math.random() * window.DynamicToolbarFunnyPhrases.length | 0].toLowerCase()
        }

        $("#dynamic-toolbar-input")
            .attr("placeholder",  window.DynamicToolbarInputPlaceholderWords[0])
            .on('click', function () {
                if ($(this).val() == '') {
                    $(this).val(window.DynamicToolbarInputPlaceholderWords[0])
                }
            })
    }



    window.DynamicToolbarInit = function () {
        var imageUrlPrefix = 'http://ift.tt/1GOJ07y'
        var crocBar = 'http://ift.tt/1GOIXIW'

        var css = ' \
        #dynamic-toolbar { \
            display: none; \
            position: fixed; \
            bottom: 0px; \
            left: 0px; \
            height: 45px; \
            margin: 0; \
            padding: 0; \
            right: 0px; \
            width: 100% !important; \
            z-index: 99999999999999; \
            -webkit-backface-visibility: hidden; \
            background-color: #f8f8f8; \
            -webkit-box-sizing: border-box; \
            -moz-box-sizing: border-box; \
            box-sizing: border-box; \
            } \
        #dynamic-toolbar #dynamic-toolbar-wrapper { \
            top: 2px; \
            font-size: 12px; \
            font: 16px/29px "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; \
            color: #000; \
            -webkit-font-smoothing: antialiased; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper form { \
            margin: 0; \
            padding: 0; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer { \
            position: relative; \
            left: 0; \
            width: 100%; \
            padding: 0; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel { \
            cursor: default; \
            position: relative; \
            width: 100%; \
            text-align: center; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner { \
            display: inline-block; \
            line-height: 19px !important; \
            width: 97%; \
            margin-top: 8px; \
            -webkit-box-sizing: border-box; \
            -moz-box-sizing: border-box; \
            box-sizing: border-box; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner input.prettysearch { \
            width: 100%; \
            height: 29px; \
            background: url(' + imageUrlPrefix + 'tm.png) center top repeat-x; \
            background-size: 1px 29px; \
            border: 0; \
            margin: 0; \
            padding: 0px 0 0 0; \
            font: 16px/19px "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; \
            line-height: 19px; \
            color: #000; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner .toolbar-left { \
            position: relative; \
            top: 0; \
            margin: 0; \
            float: left; \
            width: 5px; \
            height: 29px; \
            background: url(' + imageUrlPrefix + 'tl.png) left top no-repeat; \
            background-size: 5px 29px; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner .toolbar-middle { \
            position: relative; \
            top: 0; \
            margin: 0; \
            padding-left: 5px; \
            padding-right: 57px; \
            -webkit-box-sizing: border-box; \
            -moz-box-sizing: border-box; \
            box-sizing: border-box; \
            height: 29px; \
            width: 100%; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner .toolbar-middle input.prettysearch::placeholder { \
            text-align: center; \
            color:#000; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner .toolbar-middle input.prettysearch::-webkit-input-placeholder { \
            text-align: center; \
            color:#000; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner .toolbar-middle input.prettysearch:-moz-placeholder { \
            text-align: center; \
            color:#000; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner .toolbar-middle input.prettysearch::-moz-placeholder { \
            text-align: center; \
            color:#000; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner .toolbar-middle input.prettysearch:-ms-input-placeholder { \
            text-align: center; \
            color:#000; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner .toolbar-right { \
            position: relative; \
            top: 0; \
            margin: 0; \
            float: right; \
            width: 19px; \
            height: 29px; \
            background: url(' + imageUrlPrefix + 'trmb.png) right top no-repeat; \
            background-size: 19px 29px; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner #dynamic-toolbar-backtop-btn { \
            position: relative; \
            top: 0; \
            margin: 0; \
            float: right; \
            width: 38px; \
            height: 29px; \
            background: url(' + imageUrlPrefix + 'upbtib.png) right top no-repeat; \
            background-size: 38px 29px; \
            z-index: 9999999999999999; \
        } \
        #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner .toolbar-pixel { \
            position: relative; \
            float: right; \
            width: 0px; \
            height: 29px; \
            display: none; \
            background: url(' + crocBar + ') right top no-repeat; \
            background-size: 1px 29px; \
        } \
        input#dynamic-toolbar-input:focus { \
            outline-width: 0px; \
            outline: none; \
        } \
        #dynamic-toolbar-outer.chrome, \
        #dynamic-toolbar-outer.firefox { \
        } \
        #dynamic-toolbar-outer.chrome #dynamic-toolbar-inner input#dynamic-toolbar-input, \
        #dynamic-toolbar-outer.firefox #dynamic-toolbar-inner input#dynamic-toolbar-input { \
            display: block; \
            padding: 0px 0px 0px 0px; \
            height: 29px; \
            border-radius: 0; \
        } \
        #dynamic-toolbar-outer.chrome #dynamic-toolbar-inner input#dynamic-toolbar-input, \
        #dynamic-toolbar-outer.firefox #dynamic-toolbar-inner input#dynamic-toolbar-input { \
            display: block; \
            height: 29px; \
            padding: 0; \
            border-radius: 0; \
        } \
        #dynamic-toolbar-outer.safari #dynamic-toolbar-panel { \
            position: relative; \
        } \
        #dynamic-toolbar-outer label[for="dynamic-toolbar-input"] { \
            display: block !important; \
        } \
        @media only screen and (max-width: 500px) { \
            #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner { \
                width: 95% !important; \
            } \
        } \
        @media only screen and (max-width: 380px) { \
            #dynamic-toolbar #dynamic-toolbar-wrapper #dynamic-toolbar-outer #dynamic-toolbar-panel #dynamic-toolbar-inner { \
                width: 94% !important; \
            } \
        } \
        .dynamic-toolbar-fixfixed { \
        } \
        .dynamic-toolbar-fixfixed-temp { \
            position: relative !important; \
            bottom: 45px !important; \
        } \
        #dynamic-toolbar-innerbody { \
            height: 100%; \
            width: 100%; \
            border: 0; \
            margin: 0; \
            padding: 0; \
            overflow: hidden; \
            overflow-y: auto; \
        } \
        #dynamic-toolbar-tooltip { \
            display: none; \
            left: 10px; \
            z-index: 999999999999999999; \
            width: 100px; \
            margin-left: -127px; \
            padding: 5px; \
            border: 1px solid #007aff; \
            opacity: .8; \
            background-color: #fff; \
            border-radius: 5px; \
            color:#000; \
            text-align: center; \
            position: absolute; \
            margin: auto; \
            bottom: 40px; \
        } \
        #dynamic-toolbar-tooltip:before, \
        #dynamic-toolbar-tooltip:after { \
            content: ""; \
            position: absolute; \
            z-index: 9999999999999999999; \
            bottom: -7px; \
            left: 20px; \
            margin-left: -8px; \
            border-top: 8px solid #fff; \
            border-left: 8px solid transparent; \
            border-right: 8px solid transparent; \
            border-bottom: 0; \
        } \
        #dynamic-toolbar-tooltip:before { \
            border-top-color: #007aff; \
            bottom: -8px; \
        } \
        '

        var $css = $('<style id="test" type="text/css"></style>')
        $css.html(css)
        $('head').append($css)
//        $('body').wrapInner("<div id='#dynamic-toolbar-innerbody'></div>")

        $(window).scroll(function () {
            if ($(window).scrollTop() > (20)) {
                if ($("#dynamic-toolbar").length == 0) {
                    var target = !(/iP(hone|od|ad)/.test(navigator.platform)) ? 'target="_blank"' : ''
                    $("body").append(' \
                        <div id="dynamic-toolbar"> \
                            <div id="dynamic-toolbar-tooltip">Веб-поиск</div> \
                            <div id="dynamic-toolbar-wrapper"> \
                                <form action="//go.mail.ru/msearch" method="get" accept-charset="UTF-8" id="dynamic-toolbar-search" autocomplete="off" \
                                    ' + target + '>\
                                    <input type="hidden" name="gp" value="789005"> \
                                    <div id="dynamic-toolbar-outer" data-hires="true" class="chrome"> \
                                        <div id="dynamic-toolbar-panel"> \
                                            <div id="dynamic-toolbar-inner" class="empty blurred" data-hires="true"> \
                                                <div class="toolbar-left" data-hires-status="replaced"></div> \
                                                <div class="toolbar-pixel" data-hires-status="replaced"></div> \
                                                <div id="dynamic-toolbar-backtop-btn"></div> \
                                                <div class="toolbar-right" data-hires-status="replaced"></div> \
                                                <div class="toolbar-middle"> \
                                                    <label for="dynamic-toolbar-input" id="dynamic-toolbar-label"> \
                                                        <input type="text" name="q" placeholder="Веб-поиск или имя сайта" id="dynamic-toolbar-input" class="prettysearch" autocomplete="off" autocapitalize="off" autocorrect="off" data-hires-status="replaced"> \
                                                    </label> \
                                                </div> \
                                            </div> \
                                        </div> \
                                    </div> \
                                </form> \
                            </div> \
                        </div> \
                    ')


                    var metaKeywords = $('head meta[name=keywords]').attr('content')
                    if (metaKeywords) {
                        window.DynamicToolbarMetaKeyWords = metaKeywords.toLowerCase().split(/\s*[,;]\s*/)
                        window.DynamicToolbarInputPlaceholderChange()

                        var DTPlaceholderChange = setInterval(function () {
                            window.DynamicToolbarInputPlaceholderChange()
                        }, 15000)
                    }

                    $("#dynamic-toolbar-backtop-btn")
                        .on('click', function (e) {
                            $('body,html').animate({
                                scrollTop: 0
                            }, 800);

                            e.preventDefault();
                        })

                    $(document)
                        .on('focus', '#dynamic-toolbar-input', function (e) {
                            $('#dynamic-toolbar').addClass('dynamic-toolbar-fixfixed')
                        })
                        .on('blur', '#dynamic-toolbar-input', function (e) {
                            $('#dynamic-toolbar').removeClass('dynamic-toolbar-fixfixed')
                        })
                }
               $("#dynamic-toolbar").fadeIn('slow')
            } else {
               $("#dynamic-toolbar").fadeOut('slow')
            }

        })

        $(window).resize(function () {
            //update stuff
        })
    }

    var ver = window.DynamicToolbariOSversion()
    var AppleWebKit = ver[4]
    var isAndroidVersion = window.DynamicToolbarSuppDev()
    var isSuppRes = window.DynamicToolbarSuppRes()

    var imageUrlPrefixPreloaded = 'http://ift.tt/1GOJ07y'
    window.DynamicToolbarPreloadImages(
        imageUrlPrefixPreloaded + 'tm.png',
        imageUrlPrefixPreloaded + 'tl.png',
        imageUrlPrefixPreloaded + 'trmb.png',
        imageUrlPrefixPreloaded + 'upbtib.png'
    )

    if (((ver[0] == "OS" && ver[1] >= 8) || (ver[0] == "Android" && ver[1] >= 4 && isAndroidVersion)) && AppleWebKit >= 534 && isSuppRes) {
        if (!$) {
            var script = document.createElement("SCRIPT");
            script.src = jQueryLatestSrc
            script.type = 'text/javascript'
            document.getElementsByTagName("head")[0].appendChild(script)
        }

        $(window).load(function () {
            window.DynamicToolbarInit()
        });
   }

}(this, document, this.jQuery))


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Лаборатория тестирования на проникновение «Test lab v.7» открыта

Рады сообщить об открытии новой пентест-лаборатории «Test lab v.7» под кодовым названием «Ахиллесова пята».

01 мая 2015 г. начиная с 22:00 по московскому времени лаборатория будет открыта для участия. Как сообщалось ранее, лаборатория представляет собой типичную корпоративную сеть виртуальной компании «SecureSoft LLC», которая занимается разработкой систем обеспечения информационной безопасности.

Мировая карта атак на лабораторию

Сюжет

На этот раз вам, профессиональным хакерам, предстоит произвести взлом реальной сети виртуальной компании «SecureSoft LLC», занимающейся разработкой программного обеспечения. Ситуацию усложняет факт высокой осведомленности в области ИБ сотрудников компании. Согласно отчету наших агентов о состоянии информационной безопасности компании, ИТ-структура «SecureSoft LLC» достаточно хорошо защищена от атак. Однако, есть предположение, что уязвимые места все же имеются. Ваша цель — первыми обнаружить «Ахиллесову пяту» и получить доступ к системам «SecureSoft LLC».

Нововведения

Помимо реалистичной сети с актуальными уязвимостями мы сделали дополнительных упор на визуализацию, реализовав мировую карту атак на лабораторию и обновленный модуль статистики взлома. Мы очень долго и кропотливо реализовывали задания в лаборатории, приводя их к единому, реалистическому сюжету, только с одной целью — дать всем желающим попробовать свои силы в реальном пентесте.
Онлайн графики взлома серверов («Test lab v.6»)

P.S.
Мы разрабатываем лаборатории «Test lab» для того, чтобы дать возможность всем желающим попробовать свои силы в реальном пентесте. Участники, которым удалось пройти лабораторию первыми, размещены в зале славы PENTESTIT, среди них: Майоровский Максим (xtalf), Крахмалюк Иван (LifeDJIK), Ганиев Омар (beched), Алюшин Виктор (Av1ct0r) и другие талантливые специалисты в области ИБ. На данный момент зарегистрировано более 1100 участников. Как всегда, участие бесплатно — регистрация.
Хотим поблагодарить всех, кто принимает участие лабораториях «Test lab» и пожелать честной и красивой победы!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Протокол WS2812B на STM32 без пустых циклов и прерываний. И как сделать правильную радугу

На Хабре уже есть пара статей о работе с RGB светодиодами WS2112B, но почему-то они все используют довольно архаичный способ формирования битовой последовательности. Способ заключается в формировании точных интервалов времени с помощью пустых программных циклов. Возможно это издержки использования Arduino, но мы, конечно, уже давно перешли на ARM Cortex-M4 в лице STM32 и можем себе позволить сделать красивее.

Итак, напомню “протокол” WS2112B.

Светодиодная полоса на WS2112B имеет всего один цифровой вход – DIN, подключенный к первому светодиоду на полосе. На него подается специальная импульсная последовательность, кодирующая биты, как изображено на рисунке. У каждого светодиода есть один цифровой выход – DOUT соединенный с входом DIN следующего светодиода на полосе. Каждому светодиоду нужно передать 24-е бита (по 8-мь бит на каждый цвет: красный R, зеленый G и синий B). Таким образом, чтобы зажечь все светодиоды надо передавать 24*N бит, где N количество светодиодов на полосе. Приняв биты светодиоды загораются и статично горят пока не получат новую битовую последовательность. Каждая битовая последовательность начинается с установки DIN в лог. ноль на время не менее 50 мкс.

Как видно биты кодируются достаточно короткими импульсами с жесткими допусками. От микроконтроллера, пытающегося сформировать их программными задержками требуется как минимум запретить все прерывания чтобы невзначай не сформировался сброс или сбойный бит. Ресурсы процессорного времени здесь также расходуются нерационально, чтобы зажечь 100 светодиодов процессору нужно отработать 3 мс. Если обновлять состояние светодиодов с частотой 100 Гц, то такой” протокол” заберет 30% процессорного времени.

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

А между тем в STM32 и вообще во всех чипах на Cortex-M есть отличный механизм прямого доступа к памяти (DMA). Биты можно формировать с помощью таймеров в режиме широтно-импульсной модуляции, а каждый следующий бит извлекать из ОЗУ с помощью DMA.
На рисунке ниже показана схема взаимодействия DMA и таймера TIM4 в чипе STM32F407VET6. Отладка проводилась на моем промышленном контроллере именно с таким чипом, но с тем же успехом все можно повторить на любом чипе семейства STM32. В данном случае у меня именно вывод 8 GPIOB был свободен, чем я и воспользовался.

Далее текст инициализации таймера и контроллера:
#define     BIT(n) (1u << n)
#define     LSHIFT(v,n) (((unsigned int)(v) << n))

#define LEDS_NUM    80
#define COLRS       3

INT16U DMA_buf[LEDS_NUM+2][COLRS][8];

/*------------------------------------------------------------------------------
  Timer4 генерирует импульсы на светодиодную полосу
  Тактирование таймера идет от PCLK1 72 MHz

  Канал 3 таймера используется в режиме Compare с загрузкой по DMA регистра CCR3 для формирования битовых сигналов
 ------------------------------------------------------------------------------*/
void Timer4_init(void)
{
  TIM_TypeDef *tim = TIM4;
  RCC_TypeDef *rcc = RCC;

  rcc->APB1RSTR |= BIT(2);    // Сброс таймера 4
  rcc->APB1RSTR &= ~BIT(2);   
  rcc->APB1ENR |= BIT(2);     // Разрешаем тактирование таймера 4
  tim->CR1 = BIT(7);          //  1: TIMx_ARR register is buffered.
  tim->CR2 = 0;               
  tim->PSC = 0;               // Предделитель генерирует частоту 72 МГц
  tim->ARR = 90 - 1;          // Перегрузка таймера каждые 1.25 мкс
  tim->CCMR2 = 0
               + LSHIFT(6, 4) // OC3M: Output compare 3 mode | 110: PWM mode 1 - In upcounting, channel 1 is active as long as TIMx_CNT<TIMx_CCR1 else inactive.
               + LSHIFT(1, 3) // OC3PE: Output compare 3 preload enable
               + LSHIFT(0, 0) // CC3S: Capture/Compare 3 selection | 00: CC3 channel is configured as output
  ; 
  tim->CNT = 0;
  tim->CCR3 = 0;
  tim->DIER = BIT(11);        // Bit 11 CC3DE: Capture/Compare 3 DMA request enable. Разрешаем запросы DMA
  tim->CR1 |= BIT(0);         // Запускаем таймер
  tim->CCER = BIT(8);         // Разрешаем работы выхода, чтобы возникали сигналы для DMA
}

/*------------------------------------------------------------------------------
  Инициализация канала 2 DMA1 Stream 7
  Используется для пересылки шаблоной битов потока управления светодиодной лентой на WS2812B в таймер TMR4 работающий в режиме генерации PWM 
 ------------------------------------------------------------------------------*/
void DMA1_Stream7_Mem_to_TMR4_init(void)
{
  DMA_Stream_TypeDef *dma_ch = DMA1_Stream7;
  RCC_TypeDef *rcc = RCC;

  rcc->AHB1ENR |= BIT(21);               // Разрешаем DMA1

  dma_ch->CR = 0;    // Выключаем стрим
  dma_ch->PAR = (unsigned int)&(TIM4->CCR3) + 1;  // Назначаем адрес регистра данных ADC
  dma_ch->M0AR = (unsigned long)&DMA_buf;
  dma_ch->NDTR = (LEDS_NUM + 2) * COLRS * 8;
  dma_ch->CR =
               LSHIFT(2, 25) + // CHSEL[2:0]: Channel selection |    010: channel 2 selected
               LSHIFT(0, 23) + // MBURST: Memory burst transfer configuration | 00: single transfer
               LSHIFT(0, 21) + // PBURST[1:0]: Peripheral burst transfer configuration | 00: single transfer
               LSHIFT(0, 19) + // CT: Current target (only in double buffer mode) | 0: The current target memory is Memory 0 (addressed by the DMA_SxM0AR pointer)
               LSHIFT(0, 18) + // DBM: Double buffer mode | 0: No buffer switching at the end of transfer
               LSHIFT(3, 16) + // PL[1:0]: Priority level | 11: Very high.  PL[1:0]: Priority level
               LSHIFT(0, 15) + // PINCOS: Peripheral increment offset size | 0: The offset size for the peripheral address calculation is linked to the PSIZE
               LSHIFT(1, 13) + // MSIZE[1:0]: Memory data size | 00: 8-bit. Memory data size
               LSHIFT(1, 11) + // PSIZE[1:0]: Peripheral data size | 00: 8-bit. Peripheral data size
               LSHIFT(1, 10) + // MINC: Memory increment mode | 1: Memory address pointer is incremented after each data transfer (increment is done according to MSIZE)
               LSHIFT(0, 9) +  // PINC: Peripheral increment mode | 0: Peripheral address pointer is fixed
               LSHIFT(1, 8) +  // CIRC: Circular mode | 1: Circular mode enabled
               LSHIFT(1, 6) +  // DIR[1:0]: Data transfer direction | 01: Memory-to-peripheral
               LSHIFT(0, 5) +  // PFCTRL: Peripheral flow controller | 1: The peripheral is the flow controller
               LSHIFT(1, 4) +  // TCIE: Transfer complete interrupt enable | 1: TC interrupt enabled
               LSHIFT(0, 3) +  // HTIE: Half transfer interrupt enable | 0: HT interrupt disabled
               LSHIFT(0, 2) +  // TEIE: Transfer error interrupt enable | 0 : TE interrupt disabled
               LSHIFT(0, 1) +  // DMEIE: Direct mode error interrupt enable | 0: Direct mode error interrupt disabled
               LSHIFT(0, 0) +  // EN: Stream enable | 1: Stream enabled
               0;

  dma_ch->FCR =
                LSHIFT(0, 7) + // FEIE: FIFO error interrupt enable
                LSHIFT(1, 2) + // DMDIS: Direct mode disable | 1: Direct mode disabled. Разрешаем чтобы была возможность пересылки из байт в двухбайтовый регистр 
                LSHIFT(1, 0) + // FTH[1:0]: FIFO threshold selection | 01: 1/2 full FIFO
                0;
  dma_ch->CR |= BIT(0); //  1: Stream enabled
}


После этой инициализации начинается автоматическая пересылка битового потока из массива DMA_buf расположенного в ОЗУ на внешний вывод 8 GPIOB. Автоматически генерируется и 50-и микросекундная пауза сброса. Процессор в пересылке никак не участвует, не используются даже прерывания. Чтобы зажечь какой-либо светодиод надо просто записать соответствующее слово в массив DMA_buf по соответствующему смещению. Это делается в проекте функцией LEDstrip_set_led_state.

Нельзя сказать, что данный механизм вообще не влияет на процессор. Его работа несколько замедляется. Поскольку он разделяет вместе с DMA общий доступ к ОЗУ и системной шине. Но измерения показали, что это замедление в данном случае не превышает 0.2%
Для написания проекта использовалась среда разработки MDK-ARM Professional Version: 4.72.1.0. Частота процессора – 144 МГц, частота PCLK1 – 72 МГц. Легко переносится на платы серии STM32 MCU Discovery Kits. Весь проект выложен здесь

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

И о радуге


Дело в том, что просто линейно инкрементируя байты в слове в RGB формате цвета (битовое представление — 00000000 RRRRRRRR GGGGGGGG BBBBBBBB) нельзя на светодиодной полосе с сотней светодиодов изобразить красивую радугу. Еще трудней у этой радуги регулировать яркость, делая простые манипуляции на 32-х битном слове с RGB информацией. Для таких манипуляций используют HSV формат. Например, вся радуга будет представлена если просто линейно инкрементировать H-составляющую. Потом уже преобразовать HSV в RGB и вывести на светодиоды.
В проекте есть два конвертера HSV в RGB, один целочисленный, а другой с использованием вычислений с плавающей точкой. Визуально я отличий не увидел. Да, к сожалению, здесь STM32 отличиться негде.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Как заработать очки, даже не запуская игру

image

Как-то вечером, сидя за компьютером, я наткнулся на одну инди-игру под названием «Shoot First» (игру можно скачать абсолютно бесплатно с сайта автора, а за донат любого размера вы получите специальную версию с двумя новыми видами оружия и ещё одним видом уровней). Геймплей её довольно незамысловат — игроку необходимо бегать по этажам в поисках прохода на следующий уровень, при необходимости собирая различные предметы (карты, ключи, etc) и попутно убивая встретившихся на его пути врагов. В общем, этакий action roguelike. Несмотря на кажущуюся простоту, игра меня довольно сильно зацепила, и я потратил не один час, пытаясь добраться как можно дальше и заработать как можно больше очков.

Кстати, об очках. После смерти персонажа и ввода имени игра отображает онлайн таблицу рекордов:

image

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

Как протекал процесс, и что из этого вышло, читайте под катом (осторожно, много скриншотов).

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

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

Что ж, ладно, пойдём другим путём.

Очевидно, что для получения таблицы рекордов игра лезет в сеть, а взаимодействие с сетью в Windows, как известно, лежит на плечах WinSock, реализация которой находится в WS2_32.dll. Берём в руки WPE Pro (в отличие от, например, Wireshark'а, он умеет перехватывать пакеты конкретного приложения, что в нашем случае гораздо удобнее), указываем процесс нашей игры, умираем и смотрим на результат:

image

Как видите, игра шлёт на адрес teknopants.com GET-реквесты вида

/games/shootfirst/score12.php?alltime=15&monthly=15&weekly=15&daily=15&name=%name%&score=%score%&data=Floor%20%floor%%20%5b%player%P%5d&hash=%hash%

, где %name% — это имя игрока, %score% — кол-во очков, %floor% — этаж, на котором погиб игрок, %player% — номер игрока (за одним компьютером может играть одновременно два человека — 1P и 2P соответственно) и %hash% — хеш, необходимый, очевидно, для проверки корректности отправляемых данных.

Обратите внимание, что GET-реквест одновременно содержит информацию о том, какие данные необходимо получить (параметры alltime, weekly и daily), и о том, какие данные необходимо добавить (параметры name, score, data и hash).

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

Но перед тем, как загрузить процесс в OllyDbg, давайте проверим, не запакована ли наша игра. Берём DiE, открываем исполняемый файл игры и видим следующую картину:

image

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

Что ж, отлично. Тогда запускаем подопытного в OllyDbg и пытаемся найти место, где игра делает GET-реквесты. В WinSock есть две функции, отвечающие непосредственно за отправку данных — send и WSASend. Переключаемся на модуль нашего исполняемого файла (Alt-E -> Shoot First %version%.exe) и ищем их в списке «Intermodular calls» (right-click по окну CPU -> Search For -> All intermodular calls). Как ни странно, но здесь нет ни одной, ни другой функции. На ум приходит сразу два варианта — разработчик мог скопировать их код из WS2_32.dll напрямую в своё приложение или просто вызывать их из какого-то другого модуля. Второй вариант гораздо проще отследить, так что давайте начнём с него.

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

image

Переключаемся на него (Alt-E -> plaidscores.dll) и также ищем вызовы send и WSASend. Находится только один:

image

Ставим на него софтварный бряк (left click -> F2), умираем (разумеется, в игре) и… останавливаемся перед вызовом функции send:

image

На стеке видны аргументы, самым интересным из которых для нас является Data. Если посмотреть, что находится по этому адресу (right click -> Follow in Dump), то мы увидим уже знакомый нам GET-реквест:

image

Таким образом, мы поняли, что отправка данных на сервер осуществляется в модуле plaidscores.dll. Очевидно, что модуль Shoot First %version%.dll должен каким-то образом сообщать dll некоторые данные (как минмум, это всё те же очки, в то время как хеш, например, может генерироваться уже в dll). Вариантов передачи данных тут, конечено, в общем случае целлая масса (файлы, реестр, сокеты, etc), но в большинстве случаев разработчики просто вызывают экспортированную функцию из dll с соответствующими аргументами. Смотрим, откуда нас позвали (для этого надо открыть call stack при помощи Alt-K):

image

Как видите, для отправки данных exe-модуль зовёт нас из выделенного на скришоте места. Снимаем точку останова с функции send, прыгаем на вызов (right-click -> Show call) и ставим софтварный бряк при помощи F2. Снова умираем и смотрим на обстановку:

image

Что мы здесь видим?

Во-первых, имя экспортированной функции — psSubmit.
Во-вторых, состояние стека на момент её вызова.

К сожалению, гарантированно понять, сколько аргументов передаётся экспортированной функции, можно лишь в том случае, если их имена были декорированы (при желании можно почитать об этом, например, тут). Что ж, давайте проверим. Запускаем Dependency Walker, открываем нашу dll и смотрим на список экспортированных функций:

image

К сожалению, их имена не декорированы. В таком случае нам придётся проанализировать код перед вызовом функции psSubmit в поисках PUSH'ей. Вероятнее всего, все 4 PUSH'а в case-блоке указанного выше скриншота и есть аргументы нашей исследуемой функции. Посмотрим на них ещё раз:

image

С первым и последним аргументами вопросов возникнуть не должно — это имя игрока, этаж, на котором он умер, и 1P / 2P. Скорее всего, один из оставшихся аргументов и есть наша цель — очки. Чтобы понять, какой это конкретно из них, давайте заработаем какое-нибудь их кол-во перед смертью (до этого я умирал без набора очков). Нажимаем F9, выполняем поставленную задачу, умираем и останавливаемся на том же самом месте, но уже с другими данными на стеке:

image

Я набрал 9 очков, и значение одного из аргументов действительно изменилось — теперь на его месте красуется 0x40220000. На 9 в hex'е это не очень-то смахивает, так что давайте проведём ещё несколько экспериментов:

Кол-во очков в игре — значение аргумента
6 — 0x40180000
7 — 0x401C0000
8 — 0x40200000
9 — 0x40220000
10 — 0x40240000

Как видите, значения увеличиваются неравномерно, так что гарантированно провести обратную конвертацию прямо сейчас у нас не получится. Но давайте хотя бы проверим, что при изменении этого значения перед вызовом psSubmit игра действительно думает, что мы набрали другое кол-во очков, и отправляет на сервер поддельные данные. Умираем без зарабатывания очков, останавливаемся перед вызовом psSubmit и изменяем (left click -> Ctrl-E) значение соответствующего аргумента на, предположим, 0x40220000, т.е. 9 очков. Нажимаем F9 и наблюдаем, что наше поддельное значение действительно отправилось на сервер.

Теперь у нас остались нерешёнными две проблемы:

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

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

Раз plaidscores.dll формирует GET-реквест с «читаемым» кол-вом очков, а получает на вход «закодированный» вариант, она знает, как выполнить необходимое нам преобразование (в принципе, его знает и exe-модуль, раз он может отображать кол-во очков игроку). В связи с этим мы можем прямо сейчас взяться за изучение алгоритма декодирования, но что если есть способ проще? Мы забыли, что у нас есть хеш, который безумно напоминает MD5. Вспоминая, что в WinAPI есть функция для получения MD5-хеша (и некоторых других видов) для переданных ей данных, можно предположить, что игра просто вызывает её для получения этого хеша, так что мы сможем понять, от чего именно игра берёт хеш. Если такой вызов и есть, то он должен находиться в plaidscores.dll, ведь, как мы видели, в psSubmit передаются лишь четыре аргумента, каждый из которых не очень-то напоминает MD5-хеш. Функция, о которой идёт речь, называется CryptGetHashParam (на самом деле, там целый ряд функций, которые необходимо позвать одна за другой, но всё ведёт именно к ней), так что давайте поищем её среди «Intermodular calls». К сожалению, такой функции не нашлось.

Что ж, ничего — снова останавливаемся перед вызовом psSubmit, прыгаем внутрь этой функции по нажатию F7 и ставим хардварный бряк на область памяти с нашими очками. Для этого ищем адрес, по которому хранятся очки, в «Memory Dump» (можно воспользоваться Ctrl-G) -> right-click по первому байту -> Breakpoint -> Hardware, on access -> Dword. Нажимаем F9 и попадаем в следующее место:

image

В отличие от софтварных, хардварные брейкпоинты останавливаются на инструкции, идущей после выполнения интересующих нас действий, так что смотрим на то, что находится по адресу 0x09F60195. Здесь можно увидеть инструкцию FLD:

Pushes the source operand onto the FPU register stack. The source operand can be in singleprecision, double-precision, or double extended-precision floating-point format

Так вот оно что! Получается, значение вовсе не закодировано, а всего лишь представлено в виде числа с плавающей точкой! Если посмотреть на регистр ST0, то мы действительно увидим кол-во наших очков:

image

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

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

image

Более того, как вы видите, обращается FLD не только к исследуемому нами значению, но и к последнему неизвестному аргументу. Следовательно, это 8-байтовое число с плавающей точкой.

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

image

Впрочем, неужели это всё? Пользоваться таким решением не очень-то удобно, так что я принял решение написать отдельную программу, которая будет отправлять GET-реквест с заданными пользователем данными (для упрощения кода я убрал некоторые проверки):

#include <boost/scope_exit.hpp>

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <cstdlib>
#include <iostream>
#include <sstream>

typedef void(__cdecl *submit_proc_t)(const char*, double, const char*);

int main()
{
  HMODULE scores_dll = LoadLibraryA("PlaidScores.dll");
  if (scores_dll == NULL)
  {
    std::cerr << "Unable to load DLL \n";
    return EXIT_FAILURE;
  }
  BOOST_SCOPE_EXIT_ALL(scores_dll)
  {
    FreeLibrary(scores_dll);
  };

  submit_proc_t submit_proc = (submit_proc_t)GetProcAddress(scores_dll, "psSubmit");
  if (submit_proc == NULL)
  {
    std::cerr << "Unable to find submit procedure \n";
    return EXIT_FAILURE;
  }

  std::cout << "Enter your name: ";
  std::string name;
  std::getline(std::cin, name);

  std::cout << "Enter scores count: ";
  int scores;
  std::cin >> scores;

  std::cout << "Enter floor: ";
  int floor;
  std::cin >> floor;

  std::cout << "Enter player number: ";
  int player_number;
  std::cin >> player_number;

  std::ostringstream osstr;
  osstr << "Floor " << floor << "[" << player_number << "P]";

  submit_proc(name.c_str(), scores, osstr.str().c_str());

  std::cout << "Done \n";
}


Запускаем, взволнованно смотрим на таблицу рекордов, и… Ничего не происходит.

На первый взгляд выглядит всё так же, как и в случае с вызовом plaidscores.dll из игры. Что же пошло не так? Давайте попробуем разобраться.

Загружаем наш исполняемый файл в OllyDbg, ставим бряк на psSubmit и смотрим на стек:

image

Визуально всё выглядит точно так же, как и в случае с игрой. Может, мы ошиблись с кол-вом аргументов? Но прежде чем браться за анализ PUSH'ей перед вызовом psSubmit из exe-модуля игры, вспомните, как обычно происходит работа с динамическими библиотеками в Windows. Вы, наверное, не раз слышали, что DllMain — это функция, которая довольно сильно ограничена по тому, что в ней можно делать. Однако очень часто возникает ситуация, когда DLL необходимо инициализиовать какие-то данные при запуске, чтобы не делать это постоянно при вызове каждой экспортированной функции. В связи с этим разработчики DLL зачастую предоставляют экспортированную функцию для инициализации (а также нередко и для деинициализации), в которой совершают все необходимые им действия. Посмотрим, нет ли такой функции в plaidScores.dll при помощи Ctrl-N:

image

Как видите, она действительно есть. Запускаем игру в OllyDbg, ставим бряк на psInit, смотрим откуда нас вызвали и видим, что, вероятнее всего, она принимает два аргумента:

image

Один из них — ссылка, на которую необходимо выполнять GET-реквест (http://ift.tt/1AqHIYJ), а другой является строкой «5hoo7first12».

Основываясь на новых данных, немного изменим исходный код нашей программы:

#include <boost/scope_exit.hpp>

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <cstdlib>
#include <iostream>
#include <sstream>

typedef void(__cdecl *init_proc_t)(const char*, const char*);
typedef void(__cdecl *submit_proc_t)(const char*, double, const char*);

int main()
{
  HMODULE scores_dll = LoadLibraryA("PlaidScores.dll");
  if (scores_dll == NULL)
  {
    std::cerr << "Unable to load DLL \n";
    return EXIT_FAILURE;
  }
  BOOST_SCOPE_EXIT_ALL(scores_dll)
  {
    FreeLibrary(scores_dll);
  };

  init_proc_t init_proc = (init_proc_t)GetProcAddress(scores_dll, "psInit");
  if (init_proc == NULL)
  {
    std::cerr << "Unable to find init procedure \n";
    return EXIT_FAILURE;
  }

  submit_proc_t submit_proc = (submit_proc_t)GetProcAddress(scores_dll, "psSubmit");
  if (submit_proc == NULL)
  {
    std::cerr << "Unable to find submit procedure \n";
    return EXIT_FAILURE;
  }

  std::cout << "Enter your name: ";
  std::string name;
  std::getline(std::cin, name);

  std::cout << "Enter scores count: ";
  int scores;
  std::cin >> scores;

  std::cout << "Enter floor: ";
  int floor;
  std::cin >> floor;

  std::cout << "Enter player number: ";
  int player_number;
  std::cin >> player_number;

  std::ostringstream osstr;
  osstr << "Floor " << floor << "[" << player_number << "P]";

  init_proc("http://ift.tt/1AqHIYJ", "5hoo7first12");
  submit_proc(name.c_str(), scores, osstr.str().c_str());

  std::cout << "Done \n";
}


Запускаем и наслаждаемся — на этот раз результат появился в таблице рекордов.

Как узнать, какое кол-во очков максимальное? Тут варианта два — либо автор производит необходимые проверки прямо в dll, либо на сервере. В обоих случаях проще всего поискать в модуле строки с похожим содержимым, так что делаем right-click по окну CPU -> Search for -> All referenced text strings и внимательно пробегаемся глазами по списку. Ваше внимание должны привлечь следующие строки:

image

Ставим бряки в местах обращения к ним, а также на вызов функции send, передаём огромное кол-во очков и видим, что приложение делает GET-реквест, но после его выполнения понимает, что что-то пошло не так, и действительно обращается к строке с описанием возможной причины ошибки. После этого можно провести ряд экспериментов и наконец выяснить, что максимальное допустимое значение — 2^31 − 1. Результаты экспериментов можно наблюдать на скриншоте:

image

Кстати, номер игрока можно сделать больше двух — например, 999P работает отлично.

Послесловие


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

Надеюсь, что статья показалась кому-то интересной.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

пятница, 1 мая 2015 г.

Простое управление arduino через интернет


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

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

Конечно очень хотелось использовать современные протоколы передачи данных websockets или mqtt, которые позволили бы контролировать все процессы в реальном времени, но если с клиентом для arduino дела обстояли хорошо — наличие неплохих библиотек радовало, то вот с серверной стороной дела обстояли хуже — нужны были серверы с поддержкой нужных протоколов, которых у обычного хостера не было. А заводить свой сервер ради зажигания светодиода не хотелось. И выбор пал на старый и добрый http.

1. Как это всё работает.

У нас имеется:
— сервер на php расположенный на хостинге который привязаный к доменному имени
— клиент в виде arduino
— панель управления

Arduino подключается к серверу и отправляет GET запрос, где содержатся значения датчиков температуры.

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

Arduino принимает ответ от сервера и согласно ему устанавливает состояние своего выхода

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

2. Клиент на Arduino

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

Скетч Arduino
#include
#include
#include
#include

#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

byte mac[] = { 0x54, 0x34, 0x41, 0x30, 0x30, 0x31 };

EthernetClient client;
char server[] = "*************"; // имя вашего сервера
int buff=0;
const int led=5;

void setup()
{
Ethernet.begin(mac);
sensors.begin();
pinMode( led, OUTPUT);
digitalWrite(led, LOW);
}

void loop()
{

sensors.requestTemperatures();

if (client.connect(server, 80))
{

client.print( «GET /add_data.php?»);
client.print(«temperature=»);
client.print( sensors.getTempCByIndex(0) );
client.print("&");
client.print("&");
client.print(«temperature1=»);
client.print( sensors.getTempCByIndex(1) );
client.println( " HTTP/1.1");
client.print( «Host: » );
client.println(server);
client.println( «Connection: close» );
client.println();
client.println();

delay(200);

while (client.available())
{
char c = client.read();
if ( c=='1')
{
buff=1;
}
if ( c=='0')
{
buff=0;
}
}
client.stop();
client.flush();
delay(100);
}
else
{
client.stop();
delay(1000);
client.connect(server, 80);
}

if ( buff==1)
{
digitalWrite (led1, HIGH);
}
else
{
digitalWrite(led1, LOW);
}
delay(500);
}

3. Сервер и панель управления

Сервер состоит всего из нескольких файлов:

index.php — панель управления
add_data.php — файл обрабатывающий запросы с контроллера и отсылающий ответ обратно на arduino
style.css — определяет внешний вид панели
Папка transfer — содержит файлы с помощью котрых происходит считывание и запись значений из текстовых файлов.
led.php — записывает состояние выхода в файл out-1.txt, отправленное через форму в панели управления
ledstate.php — считывает состояние из текстового файла out-1.txt и выводит на пенели в виде «ON» или «OFF»
temp-1.php и temp-2.php — считывают значения температуры из файлов in-1.txt и in-2.txt и отправляют на панель управления.
Папка txt — своего рода база данных для хранения информации.

Сервер на самом деле очень простой и его сможет установить себе любой человек с минимальными познаниями, например, как я. До работы над этим проектом у меня был опыт работы только с arduino поэтому php, ajax, html и css пришлось изучать, буквально с нуля.

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

Уверен, что матерые программисты будут пинать меня и тыкать носом в те места где можно было бы написать код более лаконично и правильно. Я это только приветствую!!!
Если вы увидели, что некоторые вещи можно сделать проще и быстрее, то сообщите мне.

Что в итоге мы имеем?

Плюсы:
— все просто и понятно
— можно настроить под свои нужды и задачи
— хорошая стабильность
— сервер можно развернуть на любом бесплатном хостинге

Минусы:
— большое количество запросов на сервер ( некоторым хостерам это может не понравиться, в этом случае нужно увеличить паузу между запросами в скетче)
— кушает много трафика со стороны клиента ( при 1 запросе в секунду выходит около 300 Мб в сутки)
— существует небольшая задержка на включение выходов ( может быть критично для некоторых случаев)

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

Возможно, если будет интересно, напишу статью об управлении через интернет wifi модулем esp8266. Его я уже успел успешно опробовать и убедился что все работает, правда там есть свои нюансы в работе.

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

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

Все желающие могут сами посетить страницу моего сервера и проверить в действии arduino.zhodinovel.com
!!! Для изменения выхода контроллера поставьте маркер на нужное значение и нажмите «ОТПРАВИТЬ» !!!

Смотрим видео

Качаем файлы
1. Сервер
2. Скетч для arduino

Всем спасибо за внимание!

UPD. Добавил обратную связь в панель в виде фоторезистора. Когда лампочка выключена показания около 130, когда включена — 900.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Анализ изображений и видео. Сегментация изображений

сегодня в 20:30

Сегодня мы публикуем восьмую лекцию из курса «Анализ изображений и видео», прочитанного Натальей Васильевой в петербургском Computer Science Center, который создан по совместной инициативе Школы анализа данных Яндекса, JetBrains и CS-клуба.

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

  1. Введение в курс «Анализ изображений и видео»;
  2. Основы пространственной и частотной обработки изображений;
  3. Морфологическая обработка изображений;
  4. Построение признаков и сравнение изображений: глобальные признаки;
  5. Построение признаков и сравнение изображений: локальные признаки;
  6. Поиск по подобию. Поиск нечетких дубликатов;
  7. Анализ изображений и видео. Классификация изображений и распознавание объектов.

Под катом вы найдете план новой лекции и слайды.

Что есть распознанный объект.

Что такое сегментация:

  • Где используется сегментация;
  • Подзадачи сегментации.

Возможные критерии «общности»:
  • Таксономия методов сегментации;
  • Критерии «общности». Цвет;
  • Критерии «общности». Текстура;
  • Критерии «общности». Расположение относительно контура;
  • Критерии «общности». Перемещение, движение (motion);
  • Критерии «общности». Глубина (depth);
  • Критерии «общности». Глобальные.

Математические модели:
  • Использование кластеризации;
  • Кластеризация.

Метод k-средних. Основная идея:
  • Метод k-средних (k-means). Алгоритм;
  • Метод k-средних: шаг 1;
  • Метод k-средних: шаг 2;
  • Метод k-средних: шаг 3;
  • Метод k-средних: шаг 4;
  • Метод k-средних: шаг 5;
  • Сегментация методом k-средних.

Добавление пространственной информации:
  • k-Means: достоинства и недостатки;
  • Mean-shift for image segmentation;
  • Mean shift algorithm;
  • Mean shift clustering/segmentation;
  • Mean shift;
  • Mean shift clustering;
  • Mean shift segmentation results;
  • More results;
  • Mean shift: достоинства и недостатки;
  • Probabilistic clustering;
  • Expectation maximization (EM).

Иерархическая кластеризация:
  • Модель для метрического пространства;
  • Моделирование при помощи графов;
  • Automatic graph cut;
  • Segmentation by Graph Cuts;
  • Min cut;
  • But min cut is not always the best cut;
  • Normalized Cut.

Примеры сегментации:
  • Использование графов;
  • Использование 2-D решетки;
  • Математические модели;
  • Методы сегментации «сверху-вниз»;
  • Деформируемые контуры;
  • Параметризация;
  • Задание энергии контура;
  • Оптимизация;
  • Berkeley Segmentation DataSet [BSDS].

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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Разработка простого плагина для JIRA для работы с базой данных: придаем нашему плагину нормальный внешний вид

В первой части мы сделали простой плагин для JIRA для работы с базой данных. Теперь придадим нашему плагину «стандартный» внешний вид JIRA.

Для начала добавим немного функционала в наш плагин. Пусть теперь для каждого проекта будет свой список студентов, т.е. студент будет привязан к строго одному проекту, и добавим студентам фамилии на всякий случай. Соответственно, нам придется переделать и выдачу студентов. Выдавать теперь будем только студентов, привязанных к определенному проекту. Для этого нам придется переписать класс Students, добавив туда необходимы атрибуты студента; добавить в интерфейс StudentDAO (и само собой класс StudentDAOImpl) новый метод для получения списка студентов именно для проекта; и переписать в классе MyAction методы execute() и doAdd() в соответствии с новыми изменениями.

Собственно, теперь можно переходить к основному контенту.

Подумав о внешнем виде нашей вкладки, обеспечим работу с изображениями. Пусть изображения будут лежать в папке images. Тогда в файл atlassian-plugin.xml в тег <atlassian-plugin> добавляем строку

<resource type="download" name="images/" location="images/" />


Стили для логотипов на странице опишем в icon.css и добавим эти стили в atlassian-plugin.xml в тег
<resource type="download" name="icon.css" location="myaction/css/icon.css"/>


В icon.css уже используем пути к изображениям согласно тому пути, который мы указали в свойстве name, когда добавляли изображения к проекту. К примеру,
.project-config-icon48-students{
    background: transparent url(images/student-48px.png) no-repeat 0 0;
}


Собственно, правим наш success.vm под использование REST-API, изменяя тег следующим образом:
    <body>
        <div id="project-config-panel-students" class="project-config-panel">
            <div class="project-config-panel-header project-config-icon48-students">
                <h2>Students</h2>
                <p>Students description.</p>
            </div>
            <form id="project-config-students" class="loading" action="#">
                <table class="aui" id="project-config-students-table">
                    <thead>
                        <tr>
                            <th><!--icon--></th>
                            <th>name</th>
                            <th>surname</th>
                            <th>created</th>
                            <th><!--buttons--></th>
                            <th><!--throbber--></th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <div class="jira-restfultable-init">
                    <span class="jira-restfultable-throbber"></span>
                    <span class="jira-restfultable-loading">Loading</span>
                </div>
            </form>
        </div>
    </body>


Здесь нужен для отображения загрузки, а в тег
 <table>

будут выводиться студенты. Страница без REST-API выглядит следующим образом:

Добавим точку входа в наш REST-API. Для этого в файл atlassian-plugin.xml в тег <atlassian-plugin> пропишем

<rest key="pluginRESTpoints" path="/simple-api" version="1.0" />


В пакет resource.XML добавим 3 класса для работы нашего API: XmlStudent.java – представление студента при запросе и XmlStudents.java – список студентов. И Mapper.java – общий класс со статическими методами для приведения разных типов к XML виду. Подробности об использованных аннотациях можно прочитать тут.

Ну и добавляем само API. В пакет resource добавим класс StudentResource. Этот класс необходимо унаследовать от класса AbstractResource. Указать для этого класса аннотацию @Path("project/{pid}/students"), отвечающую за путь, по которому мы будем обращаться к этому классу посредством REST-API, и аннотацию @Produces({"application/json"}), отвечающую за тип передаваемых данных. В классе создадим метод getStudents, который будет выдавать список студентов. Укажем для него аннотацию @GET, соответствующую методу запроса. Параметрами метода будут: @PathParam("pid") String pid – отвечает за id проекта и берется из пути, @QueryParam("id") String id – отвечает за id студента и берется из запроса. Подробности об аннотациях можно прочитать тут.

Собственно, простое API для JIRA готово, теперь если, к примеру, в браузере перейду по адресу:

localhost:8080/rest/simple-api/1.0/project/10002/students

то я получу ответ:

{«pid»:10002,«count»:2,«students»:[{«id»:4,«name»:«Вася»,«surname»:«Пупкин»,«pid»:10002,«created»:«2014-05-07 14:48:15.83»},{«id»:5,«name»:«Игорь»,«surname»:«Петров»,«pid»:10002,«created»:«2014-05-07 17:47:58.5»}]}


Теперь надо написать js, который будет выдавать данные из API в нашу заготовку. Т.к. данные о студентах мы будем выдавать в таблицу, то использовать будем стандартный функционал JIRARestfultable. Проблемой при написании плагина стало найти документацию для версии 4.4. Поэтому здесь остановлюсь подробнее.

Для начала создадим шаблон вывода информации в таблицу – student.soy:

{namespace JIRA.Templates.Student}
/**
* @param student
*/
{template .studentRow}
<td class="jira-restfultable-icon project-config-student-icon">
    <span class="project-config-icon project-config-icon-student"></span>
</td>
<td class="project-config-student-name">
    {$student.name}
</td>
<td class="project-config-student-surname">
    {$student.surname}
</td>
<td class="project-config-student-created">
    {$student.created}
</td>
<td class="jira-restfultable-operations"></td>
<td class="project-config-throbber"></td>
{/template}


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

Для трансформации этого шаблона в js код необходимо в этот же тег еще добавить jiraSoyTransformer — элемент, который сделает из шаблона *.soy скрипт js.

  <transformation extension="soy">
            <transformer key="jiraSoyTransformer"/>
        </transformation>


Чтобы корректно работал JIRARestfultable, необходимо добавить следующий элемент для работы механизма интернационализации в этот же тег:
<transformation extension="js">
            <transformer key="jsI18n"/>
        </transformation>


Добавим скрипт StudentRow.js, который создает строки из шаблонов. Скрипты в общем плане типовые, поэтому не буду на них останавливаться.
jQuery.namespace("JIRA.Admin.Student.StudentRow");
JIRA.Admin.Student.StudentRow = JIRA.RestfulTable.Row.extend({
    initialize: function() {
        JIRA.RestfulTable.Row.prototype.initialize.apply(this, arguments);
    },
    render: function() {
        var data = this.model.toJSON(),
                id = this.model.get("id"),
                $el = this.$el;
        $el.attr("id", "student-" + id + "-row").attr("data-id", id);
        $el.html(JIRA.Templates.Student.studentRow({
            student: data
        }));
        return this;
    }
});


Теперь создадим скрипт для инициализации нашей таблицы student-init.js:
jQuery(function() {
    var $table = AJS.$("#project-config-students-table");
    var $project = AJS.$("meta[name=projectId]").attr("content");
    function getResourceURL() {
        return contextPath + "/rest/simple-api/1.0/project/" + $project + "/students";
    }
    function getStudent(callback) {
        JIRA.SmartAjax.makeRequest({
            url: getResourceURL(),
            complete: function(xhr, status, response) {
                if (response.successful) {
                    callback(response.data.students);
                } else {
                    $table.trigger("serverError",
                            [JIRA.SmartAjax.buildSimpleErrorContent(response)]);
                }
            }
        });
    }
    getStudent(function(students) {
        JIRA.Admin.StudentTable = new JIRA.RestfulTable({
            el: $table,
            url: getResourceURL(),
            entries: students,
            noEntriesMsg: 'There are currently no students for this project.',
            views: {
                row: JIRA.Admin.Student.StudentRow
            }
        });
        jQuery(".jira-restfultable-init").remove();
        JIRA.userhover($table);
    });
});

Необходимо добавить все указанные файлы в тег <my-resources>.Собственно, ничего сложного:



Теперь реализуем удаление, редактирование и добавление студентов.

Для начала сделаем все во внешнем виде, а потом уже сделаем для него REST Api.
Немного переделаем student.soy, добавив в него шаблон JIRA.Templates.Student.editStudentRow для редактирования и добавления студентов. В шаблон JIRA.Templates.Student.studentRow добавим появление стандартной подсветки JIRA для полей, которые можно редактировать, и кнопку удаления. Напишем скрипт EditStudentRow.js для создания строки для редактирования из шаблона JIRA.Templates.Student.editStudentRow. И добавим возможность редактирования в student-init.js. В StudentRow.js. в функцию getStudent добавим обработку нажатия на кнопку удаления.
Осталось добавить новый файл EditStudentRow.js в atlassian-plugin.xml в тег my-resources. И мы получили:

С внешним видом мы закончили. Разберемся, какие запросы отравляются при нажатии на каждую кнопку:

добавление:

url: http://localhost:8080/rest/simple-api/1.0/project/10000/students 
method: POST
request: {"name":"Дима","surname":"Андреев"}

удаление:

url: http://localhost:8080/rest/simple-api/1.0/project/10000/students/10?[дальше идет request]
method: DELETE
1.      request: id=10&name=Вася&surname=Пушкин&pid=10000&created=2014-05-13 15:54:12.803

изменение:

url: http://server/rest/simple-api/1.0/project/10000/students/10
method: PUT
request: {"id":10,"name":"Дима","surname":"Андреев"}

В url: 10000 – id проекта, 10 – id изменяемой строки, в request передаются измененные значения, т.е. если бы мы не меняли surname, то request был бы таким:

{"id":10,"name":"Дима"}

Перед тем как писать обработку этих запросов, дополним наш StudentDAO.java необходимыми методами добавления и удаления.
И осталось добавить в StudentResource.java обработку запросов.

Собственно, на этом все. Можно, конечно, дальше написать про MultiSelect и SingleSelect для Jira и как их использовать (свои собственные UserPicker, ProjectPicker, RolePicker и т.п.), как сделать перемещение строк внутри таблицы и много других мелочей, легко осуществимых с помощью restfultable и REST Api.

Код плагина на GitHub.

Первая часть.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Перевод] 7 вещей, которые каждый дизайнер должен знать про дизайн доступных сайтов

image

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

1. Доступность – это не препятствие для инноваций


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

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

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

2. Не ограничивайтесь цветом для передачи информации


Это поможет пользователям, у которых есть трудности с разделением цветов. 1 из 12 мужчин и 1 из 200 женщин не различают цвета, 1 из 30 людей плохо видят, 1 из 188 людей слепы.

Используйте цвет, чтобы выделять или дополнять то, что и так уже видно. Сколько полей в следующем примере находятся в состоянии «ошибка»? Большинство скажет «одно» – окно для ввода капчи содержит восклицательный знак.

image

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

image

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

3. Убедитесь в наличии достаточного контраста текста и фона


Согласно правилам WCAG (Web Content Accessibility Guidelines 2.0), этот контраст должен быть не меньше, чем 4.5 к 1. Если размер шрифта – 24 px или 19 px жирный, тогда минимум будет 3 к 1.

Например, если текст 24 px, 19 px bold или больше, то серый оттенок цвета на белом фоне должен быть не светлее, чем #959595.

image

Для текста поменьше необходимо использовать как минимум #767676. Если фон серый, текст должен быть темнее.

image

Есть несколько замечательных инструментов для подбора палитры — Color Safe, WebAIM’s Color Contrast Checker.

Логотипы или те элементы, что в данный момент выключены (disabled), являются исключением из правил. Placeholder НЕ является исключением.

Вот пример с популярного блогосайта с контрастом текста ниже нормы. Норме соответствует только буква М:

image

Следующий пример с сайта BBC показывает правильный подбор цвета – самый светлый серый цвет у них, это #767676.

image

Я с гордостью работаю в команде Salesforce Design Systems, где приняли эти стандарты по цветовому контрасту и с их учётом разработали приложение Salesforce1:

image

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

4. Обозначьте визуально, где находится фокус ввода


Сначала поблагодарим css reset за универсальность и однообразность, которую они привнесли в веб.

А теперь поругаем за то, что они испортили поля ввода вот этой формулой:

    :focus {outline: 0;}

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

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

image

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

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

Если вы убираете фокус по умолчанию – замените его чем-нибудь получше. Пример: BBC использует цветовое подчёркивание для обозначения фокуса:

image

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

image

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

image

5. Осторожнее с формами


В последнее время формы де-эволюционируют. Дизайнеры отбрасывают традиционные атрибуты и интерактивность в угоду минимализму. У форм недостаёт двух важных свойств, нужных для доступности: рамок и меток.
Формы без рамок

Перед вами – пример традиционного поля ввода. Это прямоугольник с чёткими рамками. Его можно залить цветом, но не обязательно. Есть и метка слева от поля.

image

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

Перед вами – пример поля поиска одного популярного приложения для записей:

image

Где мне кликать для ввода поискового запроса? На экране – только одно поле ввода. Можете ли вы определить, где находятся границы поля?

Вот ещё один пример полей без границ с одной из популярных блогоплатформ. Перед вами – два поля ввода. Где нужно кликнуть, чтобы ввести текст в поле «Tell your story»?

image

А вот вам та же страница, только с рамкой.

image

Дальше – ещё пример поля ввода записей. Оно не использует традиционные поля ввода, но даёт больше когнитивной информации. Заголовок расположен между двумя горизонтальными линиями, а ввод текста осуществляется после клика между двумя нижними линиями.

image

Формы без меток

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

Обычно контраст у них никакой. Из нескольких примеров ниже только у одного достаточный контраст 4.5: 1.

image

Текст из плейсхолдеров исчезает. В примерах ниже – что мне надо вводить в эти поля? На сайте JetBlue — емейл, имя, фамилию, номер? На сайте Caviar – блюдо, ресторан, адрес? Поля для цены – это минимум и максимум, более и менее или до и после?

image

Вот более удобный способ дизайна полей для цены. Видно метки, min и max, даже после ввода данных.

image

6. Чётче определяйте смысл компонентов


Вопрос: когда меню – уже не меню?
Ответ: когда оно перестаёт быть модальным.

Это одна из самых больших проблем с доступностью веб-сайтов. Почитайте W3C’s Authoring Practices for Design Patterns (шаблоны и практики веб-дизайна). Там написано, как создавать доступную версию разных частей сайтов – меню, модальные меню, автодополнение, деревья, закладки и многое другое.

Для всех шаблонов указан набор рекомендуемых HTML-элементов, поведение клавиатуры и использование атрибутов ARIA (Accessible Rich Internet Applications Suite, технологический стандарт, разрабатываемый консорциумом для предоставления возможности полноценного использования Интернета людьми с физическими ограничениями). Атрибуты предназначены для устройств чтения с экрана, и для того, чтобы сообщать, как пользоваться компонентами с клавиатуры. Они сообщают о происходящих на странице изменениях при взаимодействии с ней. Например, сообщают людям, что для навигации по меню необходимо пользоваться кнопками вниз и вверх.

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

image

А вот примерно то же самое, но с иконками:

image

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

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

image

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

То же касается и меню. На примерах ниже с сайта Virgin два примерно одинаковых поля, но только правая выпадушка – меню. Левая – не-модальный диалог.

image

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

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

7. Не заставляйте людей наводить мышь в поисках нужных вещей


Это относится к людям с ограниченной подвижностью. Это также относится к тем, кто пользуется только с клавиатурой, и тем, кто пользуется системами для чтения текста со страниц типа Dragon Naturally Speaking. Все они рассчитывают на элементы, которые видны на странице. Если их нет – на них нельзя «кликнуть» с помощью голоса. Если пользователь с клавиатурой не видит кнопку, как он сможет перейти на то место, где она «должна появиться»?

Ниже – скриншот Gmail, на котором Dragon Naturally Speaking показывает слой с пронумерованными ссылками. Пользователь проговаривает номер и переходит по ссылке. Что, если ссылка не видна, пока на неё не наведёшь мышь? Номера будут появляться рядом с пустыми местами?

Я понимаю, почему стало модно прятать вещи, пока не наведёшь на них курсор. Это внедрение общепринятого правила, сформулированного Аланом Кеем:

Простые вещи должны быть простыми, сложные – возможными.

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

Показывайте основные вещи, а второстепенные – прячьте до наведения мыши.

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

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

Вот пример с сайта LinkedIn – профиль:

image

А если я навожу мышь на эту карточку:

image

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

image

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

image

А когда я навожу на поле мышь, появляется синий цвет:

image

И тут дизайнеры скажут:

Но ведь это же утяжеляет страницу, не?

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

Вот ещё один пример с Evernote. Список заметок. Если навести мышь на ряд, появляются четыре иконки.

image

Я бы предложил дизайнеру оставить их видимыми. Их можно сделать зелёными на белом, и инвертировать при наведении мыши.

image

Возможно, это решение тоже будет «тяжёлым», но мы ведь не для дизайнеров разрабатываем. Мы делаем сайты для разнородной публики, у которой разные потребности и разные инструменты для доступа к компьютерам.

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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.