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

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

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

 

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

 -Статистика

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

Habrahabr








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

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

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

[recovery mode] Немного о доморощенном

Воскресенье, 17 Сентября 2017 г. 11:54 + в цитатник
PennyLane сегодня в 11:54 Разное

Немного о доморощенном

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

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

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

https://habrahabr.ru/post/338090/


Метки:  

ASO: ранжирование в App Store и Google Play (найди 10 отличий в алгоритмах)

Воскресенье, 17 Сентября 2017 г. 09:13 + в цитатник
Сразу оговорюсь, что точного ответа на большинство рассмотренных ниже вопросов вам не даст никто (если ваш знакомый не работает в Apple/Google и не собрался нарушить NDA), и текущую актуальную версию ответа можно получить только тестами, причем желательно на достаточно большом трафике. И никогда точно не известно, сколько продержится актуальность текущей правильной версии этих ответов.
Читать дальше ->

https://habrahabr.ru/post/338086/


Метки:  

Смарт контракты Ethereum: пишем простой контракт для ICO

Воскресенье, 17 Сентября 2017 г. 00:43 + в цитатник
isvirin сегодня в 00:43 Разработка

Смарт контракты Ethereum: пишем простой контракт для ICO

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


    Для экономии времени я написал контракт заранее. Давайте разберем его по шагам.

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

    Признаком хорошего тона считается начинать любую программу, в т.ч. и смартконтракт, с указания лицензии, на основе которой она распространяется. В нашем случае это GPL. Также можно указать себя в качестве автора контракта, конечно, если вы не пишете контракт для какого-нибудь скамового проекта, где стесняетесь указать себя в качестве автора.
    /*
    This file is part of the EasyCrowdsale Contract.
    
    The EasyCrowdsale Contract is free software: you can redistribute it and/or
    modify it under the terms of the GNU lesser General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    The EasyCrowdsale Contract is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU lesser General Public License for more details.
    
    You should have received a copy of the GNU lesser General Public License
    along with the EasyCrowdsale Contract. If not, see /www.gnu.org/licenses/>.
    
    @author Ilya Svirin <i.svirin@prover.io>
    */
    

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

    pragma solidity ^0.4.0;
    


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

    Прежде всего следует понимать, что после загрузки смартконтракта в виртуальную машину Ethereum вы будете взаимодействовать с ним на общих основаниях, как и все остальные пользователи. Логично, что мы, как команда проекта, хотели бы находиться в привилегированных условиях, которые должны как минимум выражаться в том, что контракт должен сформировать именно на командные токены и конечно же отдал именно нам собранный эфир. Для этого контракт должен знать своего владельца и именно за это отвечает контракт «owned».
    contract owned {
    
        address public owner;
    
        function owned() payable {
            owner = msg.sender;
        }
        
        modifier onlyOwner {
            require(owner == msg.sender);
            _;
        }
    
        function changeOwner(address _owner) onlyOwner public {
            owner = _owner;
        }
    }
    


    Контракт «owned» содержит лишь одно публичное поле «owner», значение которого инициализируется в конструкторе значением поля «sender» глобальной структуры «msg», таким образом, изначально владельцем контракта становится тот, кто осуществил его деплой.

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

    Контракт «owned» полностью работоспособен и предельно прост, однако несет в себе одну скрытую угрозу, ведь при вызове функции «changeOwner» мы можем по ошибке указать несуществующий адрес, а значит, потеряем контроль над контрактом. Чтобы исправить этот недостаток, достаточно ввести еще поле, назовем его «candidate», а при вызове функции «changeOwner» будем сохранять новое значение сначала в «candidate», а перемещать его в «owner» будем, как только кандидат подтвердит свое вступление в права, вызвав со своего адресу функцию «confirmOwner».

    Следующий в иерархии контракт – «Crowdsale», отвечает непосредственно за сбор средств и выдачу токенов и наследует рассмотренный ранее контракт «owned».
    contract Crowdsale is owned {
        
        uint256 public totalSupply;
        mapping (address => uint256) public balanceOf;
    
        event Transfer(address indexed from, address indexed to, uint256 value);
    
        function Crowdsale() payable owned() {
            totalSupply = 21000000;
            balanceOf[this] = 20000000;
            balanceOf[owner] = totalSupply - balanceOf[this];
            Transfer(this, owner, balanceOf[owner]);
        }
    
        function () payable {
            require(balanceOf[this] > 0);
            uint256 tokensPerOneEther = 5000;
            uint256 tokens = tokensPerOneEther * msg.value / 1000000000000000000;
            if (tokens > balanceOf[this]) {
                tokens = balanceOf[this];
                uint valueWei = tokens * 1000000000000000000 / tokensPerOneEther;
                msg.sender.transfer(msg.value - valueWei);
            }
            require(tokens > 0);
            balanceOf[msg.sender] += tokens;
            balanceOf[this] -= tokens;
            Transfer(this, msg.sender, tokens);
        }
    }
    

    Особое внимание следует обратить на следующие элементы контракта:
    • Публичное поле «totalSupply», которое должно содержать общее количество токенов, выпущенных смартконтрактом;
    • Публичная карта «balanceOf», которое содержит информацию о балансах всех держателей токенов;
    • Событие Transfer, которое должно испускаться смартконтрактом при каждой операции перемещения токенов между держателями токенов.

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

    Конструктор смартконтракта «Crowdsale» предельно прост. Прежде всего инициализируется значение поля «totalSupply». Наш контракт выпускает 21 миллион токенов, из которых 20 миллионов сразу будут перемещены на баланс смартконтракта. Будем считать, что токены с адреса смартконтракта как раз и доступны для продажи. Оставшиеся токены, в нашем случае 1 миллион, будут записаны на адрес владельца контракта. Ну и в конце конструктора испускается событие «Transfer», которое помещается в блокчейн и информирует пользователей контракта о том, что с баланса контракта на баланс владельца контракта переведено соответствующее количество токенов. Именно испускание этого события позволит etherscan.io корректно отобразить держателей токенов и их балансы.

    Ну и самая главная функция смартконтракта «Crowdsale», так называемая payback функция, которая вызывается каждый раз, когда эфир поступает на адрес нашего смартконтракта. В самом начале осуществляется проверка, что на балансе смартконтракта есть хоть какое-то количество токенов для продажи. Далее устанавливаем фиксированную цену токенов – 5000 штук за 1 эфир. Затем вычисляем, сколько токенов необходимо отправить отправителю эфира. Количество переданных в транзакции средств записано в поле «value» глобальной структуры «msg» и указано оно в «wei», поэтому при определении количества токенов осуществляем перевод «wei» в «ether».

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

    На этом собственно реализация функциональности сбора средств закончена, но теперь нужно сделать наш токен операбельным и реализовать еще некоторые функции стандарта ERC20. Это сделано в контракте «EasyToken», который наследуется от рассмотренного ранее контракта «Crowdsale».
    contract EasyToken is Crowdsale {
        
        string  public standard    = 'Token 0.1';
        string  public name        = 'EasyTokens';
        string  public symbol      = "ETN";
        uint8   public decimals    = 0;
    
        function EasyToken() payable Crowdsale() {}
    
        function transfer(address _to, uint256 _value) public {
            require(balanceOf[msg.sender] >= _value);
            balanceOf[msg.sender] -= _value;
            balanceOf[_to] += _value;
            Transfer(msg.sender, _to, _value);
        }
    }
    

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

    И наконец, единственной функцией смартконтракта «EasyToken», ради которой мы создавали этот контракт, является «transfer», которая также является частью стандарта ERC20 и позволит кошелькам пользователей осуществлять передачу токенов друг другу, переводить их на биржу и выводить их с нее. Реализация функции крайне проста, проверяется достаточность количества токенов на балансе отправителя, после чего баланс отправителя уменьшается, а баланс получателя увеличивается на запрошенное количество токенов. В конце испускается событие «Transfer». Теперь наш токен является операбельным и осталось сделать самое главное – предоставить владельцу контракта возможность вывести собранные эфиры. Это мы сделаем в контракте «EasyCrowdsale».
    contract EasyCrowdsale is EasyToken {
    
        function EasyCrowdsale() payable EasyToken() {}
        
        function withdraw() public onlyOwner {
            owner.transfer(this.balance);
        }
    }
    

    Функция «withdraw» имеет модификатор «onlyOwner», т.е. может быть вызвана только владельцем смартконтракта. Единственное, что она делает – переводит весь баланс смартконтракта на адрес владельца смартконтракта.

    Несмотря на то, что рассмотренный нами смартконтракт является полностью функционально законченным и работоспособным, использовать его в реальном проекте я бы не рекомендовал. Чрезмерное упрощение логики контракта привело к тому, что такой контракт не обеспечивает защиту интересов инвесторов в должной мере, а именно, не устанавливает срок проведения crowdsale, не устанавливает минимальной границы сбора, не возвращает средства инвесторам в случае недостижения минимальной границы, а также содержит ряд известных уязвимостей на уровне компилятора языка Solidity, например, подвержен так называемой short address attack.

    Многие из этих недостатков устранены в смартконтракте нашего проекта PROVER. Контракт PROOF загружен в Ethereum по этому адресу вместе с исходным кодом, с которым можно познакомиться. Можно даже проверить, как работает контракт, отправив на него реальный эфир, у нас как раз сейчас идет Pre-ICO:). На самом деле, мы будем рады, если вы присоединитесь к presale нашего проекта PROVER, который продлится до конца сентября. PROVER – это уникальная технология подтверждения подлинности видеоматериалов на базе блокчейн и видеоаналитики.

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

    Полезные ссылки:
    Original source: habrahabr.ru (comments, light).

    https://habrahabr.ru/post/338084/


    Метки:  

    Смарт контракты Ethereum: пишем простой контракт для ICO

    Воскресенье, 17 Сентября 2017 г. 00:43 + в цитатник
    isvirin сегодня в 00:43 Разработка

    Смарт контракты Ethereum: пишем простой контракт для ICO

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


      Для экономии времени я написал контракт заранее. Давайте разберем его по шагам.

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

      Признаком хорошего тона считается начинать любую программу, в т.ч. и смартконтракт, с указания лицензии, на основе которой она распространяется. В нашем случае это GPL. Также можно указать себя в качестве автора контракта, конечно, если вы не пишете контракт для какого-нибудь скамового проекта, где стесняетесь указать себя в качестве автора.
      /*
      This file is part of the EasyCrowdsale Contract.
      
      The EasyCrowdsale Contract is free software: you can redistribute it and/or
      modify it under the terms of the GNU lesser General Public License as published
      by the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
      
      The EasyCrowdsale Contract is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      GNU lesser General Public License for more details.
      
      You should have received a copy of the GNU lesser General Public License
      along with the EasyCrowdsale Contract. If not, see /www.gnu.org/licenses/>.
      
      @author Ilya Svirin <i.svirin@prover.io>
      */
      

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

      pragma solidity ^0.4.0;
      


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

      Прежде всего следует понимать, что после загрузки смартконтракта в виртуальную машину Ethereum вы будете взаимодействовать с ним на общих основаниях, как и все остальные пользователи. Логично, что мы, как команда проекта, хотели бы находиться в привилегированных условиях, которые должны как минимум выражаться в том, что контракт должен сформировать именно на командные токены и конечно же отдал именно нам собранный эфир. Для этого контракт должен знать своего владельца и именно за это отвечает контракт «owned».
      contract owned {
      
          address public owner;
      
          function owned() payable {
              owner = msg.sender;
          }
          
          modifier onlyOwner {
              require(owner == msg.sender);
              _;
          }
      
          function changeOwner(address _owner) onlyOwner public {
              owner = _owner;
          }
      }
      


      Контракт «owned» содержит лишь одно публичное поле «owner», значение которого инициализируется в конструкторе значением поля «sender» глобальной структуры «msg», таким образом, изначально владельцем контракта становится тот, кто осуществил его деплой.

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

      Контракт «owned» полностью работоспособен и предельно прост, однако несет в себе одну скрытую угрозу, ведь при вызове функции «changeOwner» мы можем по ошибке указать несуществующий адрес, а значит, потеряем контроль над контрактом. Чтобы исправить этот недостаток, достаточно ввести еще поле, назовем его «candidate», а при вызове функции «changeOwner» будем сохранять новое значение сначала в «candidate», а перемещать его в «owner» будем, как только кандидат подтвердит свое вступление в права, вызвав со своего адресу функцию «confirmOwner».

      Следующий в иерархии контракт – «Crowdsale», отвечает непосредственно за сбор средств и выдачу токенов и наследует рассмотренный ранее контракт «owned».
      contract Crowdsale is owned {
          
          uint256 public totalSupply;
          mapping (address => uint256) public balanceOf;
      
          event Transfer(address indexed from, address indexed to, uint256 value);
      
          function Crowdsale() payable owned() {
              totalSupply = 21000000;
              balanceOf[this] = 20000000;
              balanceOf[owner] = totalSupply - balanceOf[this];
              Transfer(this, owner, balanceOf[owner]);
          }
      
          function () payable {
              require(balanceOf[this] > 0);
              uint256 tokensPerOneEther = 5000;
              uint256 tokens = tokensPerOneEther * msg.value / 1000000000000000000;
              if (tokens > balanceOf[this]) {
                  tokens = balanceOf[this];
                  uint valueWei = tokens * 1000000000000000000 / tokensPerOneEther;
                  msg.sender.transfer(msg.value - valueWei);
              }
              require(tokens > 0);
              balanceOf[msg.sender] += tokens;
              balanceOf[this] -= tokens;
              Transfer(this, msg.sender, tokens);
          }
      }
      

      Особое внимание следует обратить на следующие элементы контракта:
      • Публичное поле «totalSupply», которое должно содержать общее количество токенов, выпущенных смартконтрактом;
      • Публичная карта «balanceOf», которое содержит информацию о балансах всех держателей токенов;
      • Событие Transfer, которое должно испускаться смартконтрактом при каждой операции перемещения токенов между держателями токенов.

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

      Конструктор смартконтракта «Crowdsale» предельно прост. Прежде всего инициализируется значение поля «totalSupply». Наш контракт выпускает 21 миллион токенов, из которых 20 миллионов сразу будут перемещены на баланс смартконтракта. Будем считать, что токены с адреса смартконтракта как раз и доступны для продажи. Оставшиеся токены, в нашем случае 1 миллион, будут записаны на адрес владельца контракта. Ну и в конце конструктора испускается событие «Transfer», которое помещается в блокчейн и информирует пользователей контракта о том, что с баланса контракта на баланс владельца контракта переведено соответствующее количество токенов. Именно испускание этого события позволит etherscan.io корректно отобразить держателей токенов и их балансы.

      Ну и самая главная функция смартконтракта «Crowdsale», так называемая payback функция, которая вызывается каждый раз, когда эфир поступает на адрес нашего смартконтракта. В самом начале осуществляется проверка, что на балансе смартконтракта есть хоть какое-то количество токенов для продажи. Далее устанавливаем фиксированную цену токенов – 5000 штук за 1 эфир. Затем вычисляем, сколько токенов необходимо отправить отправителю эфира. Количество переданных в транзакции средств записано в поле «value» глобальной структуры «msg» и указано оно в «wei», поэтому при определении количества токенов осуществляем перевод «wei» в «ether».

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

      На этом собственно реализация функциональности сбора средств закончена, но теперь нужно сделать наш токен операбельным и реализовать еще некоторые функции стандарта ERC20. Это сделано в контракте «EasyToken», который наследуется от рассмотренного ранее контракта «Crowdsale».
      contract EasyToken is Crowdsale {
          
          string  public standard    = 'Token 0.1';
          string  public name        = 'EasyTokens';
          string  public symbol      = "ETN";
          uint8   public decimals    = 0;
      
          function EasyToken() payable Crowdsale() {}
      
          function transfer(address _to, uint256 _value) public {
              require(balanceOf[msg.sender] >= _value);
              balanceOf[msg.sender] -= _value;
              balanceOf[_to] += _value;
              Transfer(msg.sender, _to, _value);
          }
      }
      

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

      И наконец, единственной функцией смартконтракта «EasyToken», ради которой мы создавали этот контракт, является «transfer», которая также является частью стандарта ERC20 и позволит кошелькам пользователей осуществлять передачу токенов друг другу, переводить их на биржу и выводить их с нее. Реализация функции крайне проста, проверяется достаточность количества токенов на балансе отправителя, после чего баланс отправителя уменьшается, а баланс получателя увеличивается на запрошенное количество токенов. В конце испускается событие «Transfer». Теперь наш токен является операбельным и осталось сделать самое главное – предоставить владельцу контракта возможность вывести собранные эфиры. Это мы сделаем в контракте «EasyCrowdsale».
      contract EasyCrowdsale is EasyToken {
      
          function EasyCrowdsale() payable EasyToken() {}
          
          function withdraw() public onlyOwner {
              owner.transfer(this.balance);
          }
      }
      

      Функция «withdraw» имеет модификатор «onlyOwner», т.е. может быть вызвана только владельцем смартконтракта. Единственное, что она делает – переводит весь баланс смартконтракта на адрес владельца смартконтракта.

      Несмотря на то, что рассмотренный нами смартконтракт является полностью функционально законченным и работоспособным, использовать его в реальном проекте я бы не рекомендовал. Чрезмерное упрощение логики контракта привело к тому, что такой контракт не обеспечивает защиту интересов инвесторов в должной мере, а именно, не устанавливает срок проведения crowdsale, не устанавливает минимальной границы сбора, не возвращает средства инвесторам в случае недостижения минимальной границы, а также содержит ряд известных уязвимостей на уровне компилятора языка Solidity, например, подвержен так называемой short address attack.

      Многие из этих недостатков устранены в смартконтракте нашего проекта PROVER. Контракт PROOF загружен в Ethereum по этому адресу вместе с исходным кодом, с которым можно познакомиться. Можно даже проверить, как работает контракт, отправив на него реальный эфир, у нас как раз сейчас идет Pre-ICO:). На самом деле, мы будем рады, если вы присоединитесь к presale нашего проекта PROVER, который продлится до конца сентября. PROVER – это уникальная технология подтверждения подлинности видеоматериалов на базе блокчейн и видеоаналитики.

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

      Полезные ссылки:
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/338084/


      Метки:  

      Мои 5 копеек про Highload Cup 2017 или история 9го места

      Суббота, 16 Сентября 2017 г. 21:00 + в цитатник
      svistunov сегодня в 21:00 Разработка

      Мои 5 копеек про Highload Cup 2017 или история 9го места

        Про Higload Cup уже было несколько статей, поэтому о том, что это было писать не буду, кто пропустил можете почитать в «История 13 места на Highload Cup 2017».

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

        1. Немного про структуру данных
        2. Парсинг JSON'а на define'ах
        3. URI unescape
        4. UTF decode
        5. HTTP Server
        6. Тюнинг сети

        и много кода.

        Велосипеды


        Первую версию я написал на Go, используя net/http и encoding/json. И она легла на 2 000 RPS. После этого net/http был заменён на fasthttp, а encoding/json на easyjson. Такая конфигурация позволила уйти спать на первом месте, но с утра я уже был кажется на третьем. Здесь возникла дилемма: оптимизировать код на Go или сразу писать на C++, чтобы иметь более гибкий инструмент ближе к финалу, когда важны будут наносекунды.

        Я выбрал второй вариант, при этом решил использовать только системные библиотеки и написать свой HTTP сервер, который не тратит время на ненужные в данном случае вещи и JSON парсер/сериализатор. Ещё изначально хотелось поиграться с libnuma и SSE 4.2 командами, но до этого не дошло, так как, забегая вперёд, самая долгая операция была write в сокет.

        Весь приведённый ниже код не является «production ready», он написан для конкретных тесткейсов конкурса, в нём нет защиты от переполнения, точнее там вообще нет никакой защиты, использовать его в таком виде не безопасно!

        Немного про структуру данных


        Есть всего 3 таблицы:



        В патронах к танку нашлось чуть больше 1 000 000 пользователей, около 800 000 location'ов и чуть больше 10 000 000 визитов.

        Сервис должен возвращать элементы из этих таблиц по Id. Первое желание было сложить их в map'ы, но к счастью Id оказались практически без пропусков, поэтому можно саллоцировать непрерывные массивы и хранить элементы там.

        Также сервис должен уметь работать с агрегированной информацией, а именно

        • возвращать список посещённых пользователем location'ов в отсортированном по дате посещения порядке
        • возвращать среднюю оценку для location'а

        Чтобы делать это эффективно, нужны индексы.

        Для каждого пользователя я завёл поле std::set, где visitsCmp позволяет хранить id визитов в отсортированном по дате визита порядке. Т.е. при выводе не нужно копировать визиты в отдельный массив и сортировать, а можно сразу выводить в сериализованном виде в буфер. Выглядит он так:

        struct visitsCmp {
            Visit* visits;
            bool operator()(const uint32_t &i, const uint32_t &j) const {
                if (visits[i].VisitedAt == visits[j].VisitedAt) {
                    return visits[i].Id < visits[j].Id;
                } else {
                    return visits[i].VisitedAt < visits[j].VisitedAt;
                }
        }
        

        В случае со средней оценкой location'а, порядок не важен, поэтому для каждого location'а я завёл поле типа std::unordered_set, в котором содержатся в визиты конкретного location'а.

        При любом добавлении/изменении визита нужно было не забывать обновлять данные в затрагиваемых индексах. В коде это выглядит так:

        bool DB::UpdateVisit(Visit& visit, bool add) {
            if (add) {
                memcpy(&visits[visit.Id], &visit, sizeof(Visit));
        
                // Добвляем визит в индексы
                users[visit.User].visits->insert(visit.Id);
                locations[visit.Location].visits->insert(visit.Id);
        
                return true;
            }
        
            // Если изменилась дата визита, то надо пересортировать визиты пользователя
            if (visit.Fields & Visit::FIELD_VISITED_AT) {
                users[visits[visit.Id].User].visits->erase(visit.Id);
        	visits[visit.Id].VisitedAt = visit.VisitedAt;
        	users[visits[visit.Id].User].visits->insert(visit.Id);
            }
        
            if (visit.Fields & Visit::FIELD_MARK) {
                visits[visit.Id].Mark = visit.Mark;
            }
        
            // Если изменилась пользователь то надо удалить у старого пользователя из индекса и добавить новому
            if (visit.Fields & Visit::FIELD_USER) {
                users[visits[visit.Id].User].visits->erase(visit.Id);
        	users[visit.User].visits->insert(visit.Id);
        
                visits[visit.Id].User = visit.User;
            }
        
            // Аналогично, если изменился location
            if (visit.Fields & Visit::FIELD_LOCATION) {
                locations[visits[visit.Id].Location].visits->erase(visit.Id);
                locations[visit.Location].visits->insert(visit.Id);
        
                visits[visit.Id].Location = visit.Location;
            }
        
            return true;
        }
        

        Вообще среднее количество элементов в индексе 10, максимальное — 150. Так что можно было бы обойтись просто массивом, что повысило бы локальность данных и не сильно замедлило модификацию.

        Парсинг JSON'а на define'ах


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

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

        {
            "id": 1,
            "email": "robosen@icloud.com",
            "first_name": "Данила",
            "last_name": "Стамленский",
            "gender": "m",
            "birth_date": 345081600
        }
        

        Т.е. довольно просто написать для него парсер на мета языке
        bool User::UmnarshalJSON(const char* data, int len) {
            JSON_SKIP_SPACES()
            JSON_START_OBJECT()
        
            while (true) {
                JSON_SKIP_SPACES()
        
                // Конец объекта
                if (data[0] == '}') {
                    return true;
        
                // Разделитель полей
                } else if (data[0] == ',') {
                    data++;
                    continue;
        
                // Поле "id"
                } else if (strncmp(data, "\"id\"", 4) == 0) {
                    data += 4;
        
                    JSON_SKIP_SPACES()
                    JSON_FIELDS_SEPARATOR()
        
                    JSON_SKIP_SPACES()
                    // Прочитать и сохранить значение в поле Id
                    JSON_LONG(Id)
        
                    // Выставить флаг, что поле Id было в JSON
                    Fields |= FIELD_ID;
        
                // Поле "lastname"
                } else if (strncmp(data, "\"last_name\"", 11) == 0) {
                    data += 11;
        
                    JSON_SKIP_SPACES()
                    JSON_FIELDS_SEPARATOR();
        
                    JSON_SKIP_SPACES()
                    // Прочитать и сохранить значение в поле Id
                    JSON_STRING(LastName)
        
                    // Выставить флаг, что поле LastName было в JSON
                    Fields |= FIELD_LAST_NAME;
        
                } else if (strncmp(data, "\"first_name\"", 12) == 0) {
                    data += 12;
        
                    JSON_SKIP_SPACES()
                    JSON_FIELDS_SEPARATOR()
        
                    JSON_SKIP_SPACES()
                    JSON_STRING(FirstName)
        
                    Fields |= FIELD_FIRST_NAME;
        
                } else if (strncmp(data, "\"email\"", 7) == 0) {
                    data += 7;
        
                    JSON_SKIP_SPACES()
                    JSON_FIELDS_SEPARATOR()
        
                    JSON_SKIP_SPACES()
                    JSON_STRING(EMail)
        
                    Fields |= FIELD_EMAIL;
        
                } else if (strncmp(data, "\"birth_date\"", 12) == 0) {
                    data += 12;
        
                    JSON_SKIP_SPACES()
                    JSON_FIELDS_SEPARATOR()
        
                    JSON_SKIP_SPACES()
                    JSON_LONG(BirthDate)
        
                    Fields |= FIELD_BIRTH_DATE;
        
                } else if (strncmp(data, "\"gender\"", 8) == 0) {
                    data += 8;
        
                    JSON_SKIP_SPACES()
                    JSON_FIELDS_SEPARATOR()
        
                    JSON_SKIP_SPACES()
                    JSON_CHAR(Gender)
        
                    Fields |= FIELD_GENDER;
        
                } else {
                    JSON_ERROR(Unknow field)
                }
        
            }
        
            return true;
        }
        

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

        #define JSON_ERROR(t) fprintf(stderr, "%s (%s:%d)\n", #t, __FILE__, __LINE__); return false;
        
        #define JSON_SKIP_SPACES() data += strspn(data, " \t\r\n")
        
        #define JSON_START_OBJECT() if (data[0] != '{') { \
                JSON_ERROR(Need {}) \
            } \
            data++;
        
        #define JSON_FIELDS_SEPARATOR() if (data[0] != ':') { \
                JSON_ERROR(Need :) \
            } \
            data++;
        
        #define JSON_LONG(field) char *endptr; \
            field = strtol(data, &endptr, 10); \
            if (data == endptr) { \
                JSON_ERROR(Invalid ## field ## value); \
            } \
            data = endptr;
        
        #define JSON_STRING(field) if (data[0] != '"') {\
                JSON_ERROR(Need dquote); \
            } \
            auto strend = strchr(data+1, '"'); \
            if (strend == NULL) { \
                JSON_ERROR(Need dquote); \
            } \
            field = strndup(data+1, strend - data - 1); \
            data = strend + 1; 
        
        #define JSON_CHAR(field) if (data[0] != '"') {\
                JSON_ERROR(Need dquote); \
            } \
            if (data[2] != '"') {\
                JSON_ERROR(Need dquote); \
            } \
            field = data[1]; \
            data += 3; 
        

        URI unescape


        В получении списка мест, которые посетил пользователь есть фильтр по стране, который может быть в виде URI encoded строки: /users/1/visits?country=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F. Для декодинга на StackOverflow было найдено замечательное решение, в которое я дописал поддержку замены + на пробел:

        int percent_decode(char* out, char* in) {
            static const char tbl[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
                    -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10,
                    11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1 };
            char c, v1, v2;
            if (in != NULL) {
                while ((c = *in++) != '\0') {
                    switch (c) {
                    case '%':
                        if (!(v1 = *in++) || (v1 = tbl[(unsigned char) v1]) < 0
                                || !(v2 = *in++)
                                || (v2 = tbl[(unsigned char) v2]) < 0) {
                            return -1;
                        }
                        c = (v1 << 4) | v2;
                        break;
                    case '+':
                        c = ' ';
                        break;
                    }
                    *out++ = c;
                }
            }
            *out = '\0';
            return 0;
        }
        

        UTF decode


        Строки в JSON объектах могут быть вида "\u0420\u043E\u0441\u0441\u0438\u044F". В общем случае это не страшно, но у нас есть сравнение со страной, поэтому одно поле нужно уметь декодировать. За основу я взял percent_decode, только в случае с Unicode не достаточно превратить \u0420 в 2 байта 0x0420, этому числу надо поставить в соответствие UTF символ. К счастью у нас только символы кириллицы и пробелы, поэтому если посмотреть на таблицу, то можно заметить, что есть всего один разрыв последовательностей между буквами «п» и «р», так что для преобразования можно использовать смещение:

        void utf_decode(char* in) {
            static const char tbl[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
                    -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10,
                    11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1 };
        
            char *out = in;
        
            while (in[0] != 0) {
                if (in[0] == '\' && in[1] == 'u') {
                    uint16_t u = tbl[in[2]] << 12 | tbl[in[3]] << 8 | tbl[in[4]] << 4 | tbl[in[5]];
                    // Все ASCII символы оставляем как есть
                    if (u < 255) {
                        out[0] = u;
                        out++;
                    } else {
                        uint16_t w;
                        // < 'р'
                        if (u >= 0x0410 && u <= 0x043f) {
                            w = u - 0x0410 + 0xd090;
                        // >= 'р'
                        } else {
                            w = u - 0x0440 + 0xd180;
                        }
        
                        out[0] = w >> 8;
                        out[1] = w;
        
                        out += 2;
                    }
                    in += 6;
                } else {
                    out[0] = in[0];
                    in++;
                    out++;
                }
            }
        
            out[0] = 0;
        }
        

        HTTP Server


        Парсер


        Из HTTP запроса нужно достать метод (GET/POST), query (path + parameters) и в случае POST запроса тело. Парсить и хранить заголовки нет смысла, за исключением заголовка Content-Lentgth для POST запросов, но как оказалось позже и это не надо, так как все запросы вмещаются в один read. В итоге получился вот такой парсер:

        ...
            auto body = inBuf.Data;
        
            const char *cendptr;
            char *endptr;
            while (true) {
                switch (state) {
                case METHOD:
                    body += strspn(body, " \r\n");
                    if (strncmp(body, "GET ", 4) == 0) {
                        method = GET;
                        body += 4;
                    } else if (strncmp(body, "POST ", 5) == 0) {
                        body += 5;
                        method = POST;
                    } else {
                        state = DONE;
                        WriteBadRequest();
                        return;
                    }
                    body += strspn(body, " ");
                    cendptr = strchr(body, ' ');
                    if (cendptr == NULL) {
                        WriteBadRequest();
                        return;
                    }
                    strncpy(path.End, body, cendptr - body);
                    path.AddLen(cendptr - body);
        
                    cendptr = strchr(cendptr + 1, '\n');
                    if (cendptr == NULL) {
                        WriteBadRequest();
                        return;
                    }
        
                    state = HEADER;
                    body = (char*) cendptr + 1;
                    break;
        
                case HEADER:
                    cendptr = strchr(body, '\n');
                    if (cendptr == NULL) {
                        WriteBadRequest();
                        return;
                    }
        
                    if (cendptr - body < 2) {
                        if (method == GET) {
                            doRequest();
                            return;
                        }
        
                        state = BODY;
                    }
                    body = (char*) cendptr + 1;
        
                case BODY:
                    requst_body = body;
                    doRequest();
                    return;
            }
        ...
        

        Routing


        Хендлеров совсем мало, поэтому просто switch по методу, а внутри поиск префикса простым сравнением:

        ...
            switch (method) {
            case GET:
                if (strncmp(path.Data, "/users", 6) == 0) {
                    handlerGetUser();
                } else if (strncmp(path.Data, "/locations", 10) == 0) {
                    handlerGetLocation();
                } else if (strncmp(path.Data, "/visits", 7) == 0) {
                    handlerGetVisit();
                } else {
                    WriteNotFound();
                }
                break;
            case POST:
                if (strncmp(path.Data, "/users", 6) == 0) {
                    handlerPostUser();
                } else if (strncmp(path.Data, "/locations", 10) == 0) {
                    handlerPostLocation();
                } else if (strncmp(path.Data, "/visits", 7) == 0) {
                    handlerPostVisit();
                } else {
                    WriteNotFound();
                }
                break;
            default:
                WriteBadRequest();
            }
        ...
        

        Keep-Alive


        Яндекс.Танк не обращает внимание на заголовок «Connection» в патронах, а смотрит только на этот заголовок в ответе от сервера. Поэтому не нужно рвать соединение, а нужно работать в режиме Keep-Alive всегда.

        Работа с сетью


        Для реализации асинхронного взаимодействия естественно был выбран epoll. Я знаю 3 популярных варианта работы с epoll в многопоточном приложении:

        1. N потоков имеют общий epoll + 1 поток ждёт accept в блокирующем режиме и регистрирует клиентские сокеты в epoll
        2. N потоков имеют N epoll'ов + 1 поток ждёт accept в блокирующем режиме и регистрирует клиентские сокеты в epoll'ах, допустим используя RoundRobin.
        3. Каждый поток имеет свой epoll, в котором зарегистрирован серверный сокет, находящийся в неблокирующем состоянии и клиентские сокеты, которое этот поток захватил.

        Я сравнивал 2 и 3 варианты и на локальных тестах третий вариант немного выиграл, выглядит он так:

        void Worker::Run() {
            int efd = epoll_create1(0);
            if (efd == -1) {
                FATAL("epoll_create1");
            }
        
            connPool = new ConnectionsPool(db);
        
            // Регистрируем серверный сокет в epoll
            auto srvConn = new Connection(sfd, defaultDb);
            struct epoll_event event;
            event.data.ptr = srvConn;
            event.events = EPOLLIN;
            if (epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event) == -1) {
                perror("epoll_ctl");
                abort();
            }
        
            struct epoll_event *events;
            events = (epoll_event*) calloc(MAXEVENTS, sizeof event);
        
            while (true) {
                auto n = epoll_wait()(efd, events, MAXEVENTS, -1);
                for (auto i = 0; i < n; i++) {
                    auto conn = (Connection*) events[i].data.ptr;
        
                    if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP)
                            || (!(events[i].events & EPOLLIN))) {
                        /* An error has occured on this fd, or the socket is not
                         ready for reading (why were we notified then?) */
                        fprintf(stderr, "epoll error\n");
                        close(conn->fd);
                        if (conn != srvConn) {
                            connPool->PutConnection(conn);
                        }
                        continue;
        
                    // Если событие пришло для серверного сокета, то нужно сделать accept
                    } else if (conn == srvConn) {
                        /* We have a notification on the listening socket, which
                         means one or more incoming connections. */
                        struct sockaddr in_addr;
                        socklen_t in_len;
        
                        in_len = sizeof in_addr;
                        int infd = accept4(sfd, &in_addr, &in_len, SOCK_NONBLOCK);
                        if (infd == -1) {
                            if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
                                continue;
                            } else {
                                perror("accept");
                                continue;;
                            }
                        }
        
                        int val = true;
                        if (setsockopt(infd, IPPROTO_TCP, TCP_NODELAY, &val,
                                sizeof(val)) == -1) {
                            perror("TCP_NODELAY");
                        }
        
                        event.data.ptr = connPool->GetConnection(infd);
                        event.events = EPOLLIN | EPOLLET;
                        if (epoll_ctl(efd, EPOLL_CTL_ADD, infd, &event) == -1) {
                            perror("epoll_ctl");
                            abort();
                        }
                        continue;
        
                    // Событие для клиентского сокета, надо подготовить и отправить ответ
                    } else {
                        bool done = false;
                        bool closeFd = false;
        
                        while (true) {
                            ssize_t count;
        
                            count = read(conn->fd, conn->inBuf.Data, conn->inBuf.Capacity);
                            conn->inBuf.AddLen(count);
                            if (count == -1) {
                                /* If errno == EAGAIN, that means we have read all
                                 data. So go back to the main loop. */
                                if (errno != EAGAIN) {
                                    perror("read");
                                    done = true;
                                } else {
                                    continue;
                                }
                                break;
                            } else if (count == 0) {
                                /* End of file. The remote has closed the connection. */
                                done = true;
                                closeFd = true;
                                break;
                            }
        
                            if (!done) {
                                done = conn->ProcessEvent();
                                break;
                            }
                        }
        
                        if (done) {
                            if (closeFd) {
                                close(conn->fd);
                                connPool->PutConnection(conn);
                            } else {
                                conn->Reset(conn->fd);
                            }
                        }
                    }
                }
            }
        }
        

        Уже после закрытия приёма решений я решил отказаться от epoll и сделать классическую префорк модель, только с 1 500 потоков (Я.Танк открывал 1000+ соединений). По умолчанию каждый поток резервирует 8MB под стек, что даёт 1 500 * 8MB = 11,7GB. А по условиям конкурса приложению выделяется 4GB RAM. Но к счастью размер стека можно уменьшить с помощью функции pthread_attr_setstacksize. Минимальный размер стека — 16KB. Т.к. внутри потоков у меня ничего большого в стек не кладётся я выбрал размер стека 32KB:

            pthread_attr_t attr;
            pthread_attr_init(&attr);
        
            if (pthread_attr_setstacksize(&attr, 32 * 1024) != 0) {
                perror("pthread_attr_setstacksize");
            }
        
            pthread_create(&thr, &attr, &runInThread, (void*) this);
        

        Теперь потоки занимают 1 500 * 32KB = 47MB.
        На локальных тестах такое решение показало результаты чуть хуже чем epoll.

        Тюнинг сети


        Для профайлинга я использовал gperftools, который показал, что самая долгая операция была std::to_string. Это было довольно быстро исправлено, но теперь основное время было в операциях epoll_wait, write и writev. На первое я не обратил внимания, что, возможно, стоило попадания в призёры, а что делать с write начал изучать, попутно находя ускорения для accept

        TCP_NODELAY


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

            int val = 1;
            if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) == -1) {
                perror("TCP_NODELAY");
            }
        

        TCP_DEFER_ACCEPT


        Данная опция позволяет отправлять ответ не дожидаясь ACK'а от клиента при TCP handshake'е:

            int val = 1;
            if (setsockopt(sfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val)) == -1) {
                perror("TCP_DEFER_ACCEPT");
            }
        

        TCP_QUICKACK


        На всякий случай выставил и эту опцию, хотя до конца не понимаю принцип её работы:

            int val = 1;
            if (setsockopt(sfd, IPPROTO_TCP, TCP_QUICKACK, &val, sizeof(val)) == -1) {
                perror("TCP_QUICKACK");
            }
        

        SO_SNDBUF и SO_RCVBUF


        Размеры буферов тоже влияют на скорость передачи сети. По умолчанию используется около 16KB. Без изменения настроек ядра их можно увеличить до примерно 400KB, хотя попросить можно любой размер:

            int sndsize = 2 * 1024 * 1024;
            if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, &sndsize, (int) sizeof(sndsize)) == -1) {
                perror("SO_SNDBUF");
            }
        
            if (setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, &sndsize, (int) sizeof(sndsize)) == -1) {
                perror("SO_RCVBUF");
            }
        

        При таком размере появились битые пакеты и таймауты.

        accept4


        Обычно используется функция accept для получения клиентского сокета и 2 вызова fcntl для выставления флага fcntl. Вместо 3 системных вызова нужно использовать accept4, которая позволяет сделать тоже самое передав последним аргументом флаг SOCK_NONBLOCK за 1 системный вызов:

            int infd = accept4(sfd, &in_addr, &in_len, SOCK_NONBLOCK);
        

        aio


        Ещё 1 способ работать с IO асинхронно. В aio есть функция lio_listio, позволяющая объединить в 1 системный вызов несколько write/read, что должно уменьшить задержки на переключение в пространство ядра.

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

        epoll_wait(...., -1) -> epoll_wait(...., 0)


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

        Postscriptum


        Спасибо организаторам за конкурс, хоть всё проходило не очень гладко. Было очень увлекательно и познавательно.
        Original source: habrahabr.ru (comments, light).

        https://habrahabr.ru/post/337854/


        Метки:  

        Мои 5 копеек про Highload Cup 2017 или история 9го места

        Суббота, 16 Сентября 2017 г. 21:00 + в цитатник
        svistunov сегодня в 21:00 Разработка

        Мои 5 копеек про Highload Cup 2017 или история 9го места

          Про Higload Cup уже было несколько статей, поэтому о том, что это было писать не буду, кто пропустил можете почитать в «История 13 места на Highload Cup 2017».

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

          1. Немного про структуру данных
          2. Парсинг JSON'а на define'ах
          3. URI unescape
          4. UTF decode
          5. HTTP Server
          6. Тюнинг сети

          и много кода.

          Велосипеды


          Первую версию я написал на Go, используя net/http и encoding/json. И она легла на 2 000 RPS. После этого net/http был заменён на fasthttp, а encoding/json на easyjson. Такая конфигурация позволила уйти спать на первом месте, но с утра я уже был кажется на третьем. Здесь возникла дилемма: оптимизировать код на Go или сразу писать на C++, чтобы иметь более гибкий инструмент ближе к финалу, когда важны будут наносекунды.

          Я выбрал второй вариант, при этом решил использовать только системные библиотеки и написать свой HTTP сервер, который не тратит время на ненужные в данном случае вещи и JSON парсер/сериализатор. Ещё изначально хотелось поиграться с libnuma и SSE 4.2 командами, но до этого не дошло, так как, забегая вперёд, самая долгая операция была write в сокет.

          Весь приведённый ниже код не является «production ready», он написан для конкретных тесткейсов конкурса, в нём нет защиты от переполнения, точнее там вообще нет никакой защиты, использовать его в таком виде не безопасно!

          Немного про структуру данных


          Есть всего 3 таблицы:



          В патронах к танку нашлось чуть больше 1 000 000 пользователей, около 800 000 location'ов и чуть больше 10 000 000 визитов.

          Сервис должен возвращать элементы из этих таблиц по Id. Первое желание было сложить их в map'ы, но к счастью Id оказались практически без пропусков, поэтому можно саллоцировать непрерывные массивы и хранить элементы там.

          Также сервис должен уметь работать с агрегированной информацией, а именно

          • возвращать список посещённых пользователем location'ов в отсортированном по дате посещения порядке
          • возвращать среднюю оценку для location'а

          Чтобы делать это эффективно, нужны индексы.

          Для каждого пользователя я завёл поле std::set, где visitsCmp позволяет хранить id визитов в отсортированном по дате визита порядке. Т.е. при выводе не нужно копировать визиты в отдельный массив и сортировать, а можно сразу выводить в сериализованном виде в буфер. Выглядит он так:

          struct visitsCmp {
              Visit* visits;
              bool operator()(const uint32_t &i, const uint32_t &j) const {
                  if (visits[i].VisitedAt == visits[j].VisitedAt) {
                      return visits[i].Id < visits[j].Id;
                  } else {
                      return visits[i].VisitedAt < visits[j].VisitedAt;
                  }
          }
          

          В случае со средней оценкой location'а, порядок не важен, поэтому для каждого location'а я завёл поле типа std::unordered_set, в котором содержатся в визиты конкретного location'а.

          При любом добавлении/изменении визита нужно было не забывать обновлять данные в затрагиваемых индексах. В коде это выглядит так:

          bool DB::UpdateVisit(Visit& visit, bool add) {
              if (add) {
                  memcpy(&visits[visit.Id], &visit, sizeof(Visit));
          
                  // Добвляем визит в индексы
                  users[visit.User].visits->insert(visit.Id);
                  locations[visit.Location].visits->insert(visit.Id);
          
                  return true;
              }
          
              // Если изменилась дата визита, то надо пересортировать визиты пользователя
              if (visit.Fields & Visit::FIELD_VISITED_AT) {
                  users[visits[visit.Id].User].visits->erase(visit.Id);
          	visits[visit.Id].VisitedAt = visit.VisitedAt;
          	users[visits[visit.Id].User].visits->insert(visit.Id);
              }
          
              if (visit.Fields & Visit::FIELD_MARK) {
                  visits[visit.Id].Mark = visit.Mark;
              }
          
              // Если изменилась пользователь то надо удалить у старого пользователя из индекса и добавить новому
              if (visit.Fields & Visit::FIELD_USER) {
                  users[visits[visit.Id].User].visits->erase(visit.Id);
          	users[visit.User].visits->insert(visit.Id);
          
                  visits[visit.Id].User = visit.User;
              }
          
              // Аналогично, если изменился location
              if (visit.Fields & Visit::FIELD_LOCATION) {
                  locations[visits[visit.Id].Location].visits->erase(visit.Id);
                  locations[visit.Location].visits->insert(visit.Id);
          
                  visits[visit.Id].Location = visit.Location;
              }
          
              return true;
          }
          

          Вообще среднее количество элементов в индексе 10, максимальное — 150. Так что можно было бы обойтись просто массивом, что повысило бы локальность данных и не сильно замедлило модификацию.

          Парсинг JSON'а на define'ах


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

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

          {
              "id": 1,
              "email": "robosen@icloud.com",
              "first_name": "Данила",
              "last_name": "Стамленский",
              "gender": "m",
              "birth_date": 345081600
          }
          

          Т.е. довольно просто написать для него парсер на мета языке
          bool User::UmnarshalJSON(const char* data, int len) {
              JSON_SKIP_SPACES()
              JSON_START_OBJECT()
          
              while (true) {
                  JSON_SKIP_SPACES()
          
                  // Конец объекта
                  if (data[0] == '}') {
                      return true;
          
                  // Разделитель полей
                  } else if (data[0] == ',') {
                      data++;
                      continue;
          
                  // Поле "id"
                  } else if (strncmp(data, "\"id\"", 4) == 0) {
                      data += 4;
          
                      JSON_SKIP_SPACES()
                      JSON_FIELDS_SEPARATOR()
          
                      JSON_SKIP_SPACES()
                      // Прочитать и сохранить значение в поле Id
                      JSON_LONG(Id)
          
                      // Выставить флаг, что поле Id было в JSON
                      Fields |= FIELD_ID;
          
                  // Поле "lastname"
                  } else if (strncmp(data, "\"last_name\"", 11) == 0) {
                      data += 11;
          
                      JSON_SKIP_SPACES()
                      JSON_FIELDS_SEPARATOR();
          
                      JSON_SKIP_SPACES()
                      // Прочитать и сохранить значение в поле Id
                      JSON_STRING(LastName)
          
                      // Выставить флаг, что поле LastName было в JSON
                      Fields |= FIELD_LAST_NAME;
          
                  } else if (strncmp(data, "\"first_name\"", 12) == 0) {
                      data += 12;
          
                      JSON_SKIP_SPACES()
                      JSON_FIELDS_SEPARATOR()
          
                      JSON_SKIP_SPACES()
                      JSON_STRING(FirstName)
          
                      Fields |= FIELD_FIRST_NAME;
          
                  } else if (strncmp(data, "\"email\"", 7) == 0) {
                      data += 7;
          
                      JSON_SKIP_SPACES()
                      JSON_FIELDS_SEPARATOR()
          
                      JSON_SKIP_SPACES()
                      JSON_STRING(EMail)
          
                      Fields |= FIELD_EMAIL;
          
                  } else if (strncmp(data, "\"birth_date\"", 12) == 0) {
                      data += 12;
          
                      JSON_SKIP_SPACES()
                      JSON_FIELDS_SEPARATOR()
          
                      JSON_SKIP_SPACES()
                      JSON_LONG(BirthDate)
          
                      Fields |= FIELD_BIRTH_DATE;
          
                  } else if (strncmp(data, "\"gender\"", 8) == 0) {
                      data += 8;
          
                      JSON_SKIP_SPACES()
                      JSON_FIELDS_SEPARATOR()
          
                      JSON_SKIP_SPACES()
                      JSON_CHAR(Gender)
          
                      Fields |= FIELD_GENDER;
          
                  } else {
                      JSON_ERROR(Unknow field)
                  }
          
              }
          
              return true;
          }
          

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

          #define JSON_ERROR(t) fprintf(stderr, "%s (%s:%d)\n", #t, __FILE__, __LINE__); return false;
          
          #define JSON_SKIP_SPACES() data += strspn(data, " \t\r\n")
          
          #define JSON_START_OBJECT() if (data[0] != '{') { \
                  JSON_ERROR(Need {}) \
              } \
              data++;
          
          #define JSON_FIELDS_SEPARATOR() if (data[0] != ':') { \
                  JSON_ERROR(Need :) \
              } \
              data++;
          
          #define JSON_LONG(field) char *endptr; \
              field = strtol(data, &endptr, 10); \
              if (data == endptr) { \
                  JSON_ERROR(Invalid ## field ## value); \
              } \
              data = endptr;
          
          #define JSON_STRING(field) if (data[0] != '"') {\
                  JSON_ERROR(Need dquote); \
              } \
              auto strend = strchr(data+1, '"'); \
              if (strend == NULL) { \
                  JSON_ERROR(Need dquote); \
              } \
              field = strndup(data+1, strend - data - 1); \
              data = strend + 1; 
          
          #define JSON_CHAR(field) if (data[0] != '"') {\
                  JSON_ERROR(Need dquote); \
              } \
              if (data[2] != '"') {\
                  JSON_ERROR(Need dquote); \
              } \
              field = data[1]; \
              data += 3; 
          

          URI unescape


          В получении списка мест, которые посетил пользователь есть фильтр по стране, который может быть в виде URI encoded строки: /users/1/visits?country=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F. Для декодинга на StackOverflow было найдено замечательное решение, в которое я дописал поддержку замены + на пробел:

          int percent_decode(char* out, char* in) {
              static const char tbl[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
                      -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10,
                      11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1 };
              char c, v1, v2;
              if (in != NULL) {
                  while ((c = *in++) != '\0') {
                      switch (c) {
                      case '%':
                          if (!(v1 = *in++) || (v1 = tbl[(unsigned char) v1]) < 0
                                  || !(v2 = *in++)
                                  || (v2 = tbl[(unsigned char) v2]) < 0) {
                              return -1;
                          }
                          c = (v1 << 4) | v2;
                          break;
                      case '+':
                          c = ' ';
                          break;
                      }
                      *out++ = c;
                  }
              }
              *out = '\0';
              return 0;
          }
          

          UTF decode


          Строки в JSON объектах могут быть вида "\u0420\u043E\u0441\u0441\u0438\u044F". В общем случае это не страшно, но у нас есть сравнение со страной, поэтому одно поле нужно уметь декодировать. За основу я взял percent_decode, только в случае с Unicode не достаточно превратить \u0420 в 2 байта 0x0420, этому числу надо поставить в соответствие UTF символ. К счастью у нас только символы кириллицы и пробелы, поэтому если посмотреть на таблицу, то можно заметить, что есть всего один разрыв последовательностей между буквами «п» и «р», так что для преобразования можно использовать смещение:

          void utf_decode(char* in) {
              static const char tbl[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
                      -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10,
                      11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                      -1, -1, -1, -1, -1 };
          
              char *out = in;
          
              while (in[0] != 0) {
                  if (in[0] == '\' && in[1] == 'u') {
                      uint16_t u = tbl[in[2]] << 12 | tbl[in[3]] << 8 | tbl[in[4]] << 4 | tbl[in[5]];
                      // Все ASCII символы оставляем как есть
                      if (u < 255) {
                          out[0] = u;
                          out++;
                      } else {
                          uint16_t w;
                          // < 'р'
                          if (u >= 0x0410 && u <= 0x043f) {
                              w = u - 0x0410 + 0xd090;
                          // >= 'р'
                          } else {
                              w = u - 0x0440 + 0xd180;
                          }
          
                          out[0] = w >> 8;
                          out[1] = w;
          
                          out += 2;
                      }
                      in += 6;
                  } else {
                      out[0] = in[0];
                      in++;
                      out++;
                  }
              }
          
              out[0] = 0;
          }
          

          HTTP Server


          Парсер


          Из HTTP запроса нужно достать метод (GET/POST), query (path + parameters) и в случае POST запроса тело. Парсить и хранить заголовки нет смысла, за исключением заголовка Content-Lentgth для POST запросов, но как оказалось позже и это не надо, так как все запросы вмещаются в один read. В итоге получился вот такой парсер:

          ...
              auto body = inBuf.Data;
          
              const char *cendptr;
              char *endptr;
              while (true) {
                  switch (state) {
                  case METHOD:
                      body += strspn(body, " \r\n");
                      if (strncmp(body, "GET ", 4) == 0) {
                          method = GET;
                          body += 4;
                      } else if (strncmp(body, "POST ", 5) == 0) {
                          body += 5;
                          method = POST;
                      } else {
                          state = DONE;
                          WriteBadRequest();
                          return;
                      }
                      body += strspn(body, " ");
                      cendptr = strchr(body, ' ');
                      if (cendptr == NULL) {
                          WriteBadRequest();
                          return;
                      }
                      strncpy(path.End, body, cendptr - body);
                      path.AddLen(cendptr - body);
          
                      cendptr = strchr(cendptr + 1, '\n');
                      if (cendptr == NULL) {
                          WriteBadRequest();
                          return;
                      }
          
                      state = HEADER;
                      body = (char*) cendptr + 1;
                      break;
          
                  case HEADER:
                      cendptr = strchr(body, '\n');
                      if (cendptr == NULL) {
                          WriteBadRequest();
                          return;
                      }
          
                      if (cendptr - body < 2) {
                          if (method == GET) {
                              doRequest();
                              return;
                          }
          
                          state = BODY;
                      }
                      body = (char*) cendptr + 1;
          
                  case BODY:
                      requst_body = body;
                      doRequest();
                      return;
              }
          ...
          

          Routing


          Хендлеров совсем мало, поэтому просто switch по методу, а внутри поиск префикса простым сравнением:

          ...
              switch (method) {
              case GET:
                  if (strncmp(path.Data, "/users", 6) == 0) {
                      handlerGetUser();
                  } else if (strncmp(path.Data, "/locations", 10) == 0) {
                      handlerGetLocation();
                  } else if (strncmp(path.Data, "/visits", 7) == 0) {
                      handlerGetVisit();
                  } else {
                      WriteNotFound();
                  }
                  break;
              case POST:
                  if (strncmp(path.Data, "/users", 6) == 0) {
                      handlerPostUser();
                  } else if (strncmp(path.Data, "/locations", 10) == 0) {
                      handlerPostLocation();
                  } else if (strncmp(path.Data, "/visits", 7) == 0) {
                      handlerPostVisit();
                  } else {
                      WriteNotFound();
                  }
                  break;
              default:
                  WriteBadRequest();
              }
          ...
          

          Keep-Alive


          Яндекс.Танк не обращает внимание на заголовок «Connection» в патронах, а смотрит только на этот заголовок в ответе от сервера. Поэтому не нужно рвать соединение, а нужно работать в режиме Keep-Alive всегда.

          Работа с сетью


          Для реализации асинхронного взаимодействия естественно был выбран epoll. Я знаю 3 популярных варианта работы с epoll в многопоточном приложении:

          1. N потоков имеют общий epoll + 1 поток ждёт accept в блокирующем режиме и регистрирует клиентские сокеты в epoll
          2. N потоков имеют N epoll'ов + 1 поток ждёт accept в блокирующем режиме и регистрирует клиентские сокеты в epoll'ах, допустим используя RoundRobin.
          3. Каждый поток имеет свой epoll, в котором зарегистрирован серверный сокет, находящийся в неблокирующем состоянии и клиентские сокеты, которое этот поток захватил.

          Я сравнивал 2 и 3 варианты и на локальных тестах третий вариант немного выиграл, выглядит он так:

          void Worker::Run() {
              int efd = epoll_create1(0);
              if (efd == -1) {
                  FATAL("epoll_create1");
              }
          
              connPool = new ConnectionsPool(db);
          
              // Регистрируем серверный сокет в epoll
              auto srvConn = new Connection(sfd, defaultDb);
              struct epoll_event event;
              event.data.ptr = srvConn;
              event.events = EPOLLIN;
              if (epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event) == -1) {
                  perror("epoll_ctl");
                  abort();
              }
          
              struct epoll_event *events;
              events = (epoll_event*) calloc(MAXEVENTS, sizeof event);
          
              while (true) {
                  auto n = epoll_wait()(efd, events, MAXEVENTS, -1);
                  for (auto i = 0; i < n; i++) {
                      auto conn = (Connection*) events[i].data.ptr;
          
                      if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP)
                              || (!(events[i].events & EPOLLIN))) {
                          /* An error has occured on this fd, or the socket is not
                           ready for reading (why were we notified then?) */
                          fprintf(stderr, "epoll error\n");
                          close(conn->fd);
                          if (conn != srvConn) {
                              connPool->PutConnection(conn);
                          }
                          continue;
          
                      // Если событие пришло для серверного сокета, то нужно сделать accept
                      } else if (conn == srvConn) {
                          /* We have a notification on the listening socket, which
                           means one or more incoming connections. */
                          struct sockaddr in_addr;
                          socklen_t in_len;
          
                          in_len = sizeof in_addr;
                          int infd = accept4(sfd, &in_addr, &in_len, SOCK_NONBLOCK);
                          if (infd == -1) {
                              if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
                                  continue;
                              } else {
                                  perror("accept");
                                  continue;;
                              }
                          }
          
                          int val = true;
                          if (setsockopt(infd, IPPROTO_TCP, TCP_NODELAY, &val,
                                  sizeof(val)) == -1) {
                              perror("TCP_NODELAY");
                          }
          
                          event.data.ptr = connPool->GetConnection(infd);
                          event.events = EPOLLIN | EPOLLET;
                          if (epoll_ctl(efd, EPOLL_CTL_ADD, infd, &event) == -1) {
                              perror("epoll_ctl");
                              abort();
                          }
                          continue;
          
                      // Событие для клиентского сокета, надо подготовить и отправить ответ
                      } else {
                          bool done = false;
                          bool closeFd = false;
          
                          while (true) {
                              ssize_t count;
          
                              count = read(conn->fd, conn->inBuf.Data, conn->inBuf.Capacity);
                              conn->inBuf.AddLen(count);
                              if (count == -1) {
                                  /* If errno == EAGAIN, that means we have read all
                                   data. So go back to the main loop. */
                                  if (errno != EAGAIN) {
                                      perror("read");
                                      done = true;
                                  } else {
                                      continue;
                                  }
                                  break;
                              } else if (count == 0) {
                                  /* End of file. The remote has closed the connection. */
                                  done = true;
                                  closeFd = true;
                                  break;
                              }
          
                              if (!done) {
                                  done = conn->ProcessEvent();
                                  break;
                              }
                          }
          
                          if (done) {
                              if (closeFd) {
                                  close(conn->fd);
                                  connPool->PutConnection(conn);
                              } else {
                                  conn->Reset(conn->fd);
                              }
                          }
                      }
                  }
              }
          }
          

          Уже после закрытия приёма решений я решил отказаться от epoll и сделать классическую префорк модель, только с 1 500 потоков (Я.Танк открывал 1000+ соединений). По умолчанию каждый поток резервирует 8MB под стек, что даёт 1 500 * 8MB = 11,7GB. А по условиям конкурса приложению выделяется 4GB RAM. Но к счастью размер стека можно уменьшить с помощью функции pthread_attr_setstacksize. Минимальный размер стека — 16KB. Т.к. внутри потоков у меня ничего большого в стек не кладётся я выбрал размер стека 32KB:

              pthread_attr_t attr;
              pthread_attr_init(&attr);
          
              if (pthread_attr_setstacksize(&attr, 32 * 1024) != 0) {
                  perror("pthread_attr_setstacksize");
              }
          
              pthread_create(&thr, &attr, &runInThread, (void*) this);
          

          Теперь потоки занимают 1 500 * 32KB = 47MB.
          На локальных тестах такое решение показало результаты чуть хуже чем epoll.

          Тюнинг сети


          Для профайлинга я использовал gperftools, который показал, что самая долгая операция была std::to_string. Это было довольно быстро исправлено, но теперь основное время было в операциях epoll_wait, write и writev. На первое я не обратил внимания, что, возможно, стоило попадания в призёры, а что делать с write начал изучать, попутно находя ускорения для accept

          TCP_NODELAY


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

              int val = 1;
              if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) == -1) {
                  perror("TCP_NODELAY");
              }
          

          TCP_DEFER_ACCEPT


          Данная опция позволяет отправлять ответ не дожидаясь ACK'а от клиента при TCP handshake'е:

              int val = 1;
              if (setsockopt(sfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val)) == -1) {
                  perror("TCP_DEFER_ACCEPT");
              }
          

          TCP_QUICKACK


          На всякий случай выставил и эту опцию, хотя до конца не понимаю принцип её работы:

              int val = 1;
              if (setsockopt(sfd, IPPROTO_TCP, TCP_QUICKACK, &val, sizeof(val)) == -1) {
                  perror("TCP_QUICKACK");
              }
          

          SO_SNDBUF и SO_RCVBUF


          Размеры буферов тоже влияют на скорость передачи сети. По умолчанию используется около 16KB. Без изменения настроек ядра их можно увеличить до примерно 400KB, хотя попросить можно любой размер:

              int sndsize = 2 * 1024 * 1024;
              if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, &sndsize, (int) sizeof(sndsize)) == -1) {
                  perror("SO_SNDBUF");
              }
          
              if (setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, &sndsize, (int) sizeof(sndsize)) == -1) {
                  perror("SO_RCVBUF");
              }
          

          При таком размере появились битые пакеты и таймауты.

          accept4


          Обычно используется функция accept для получения клиентского сокета и 2 вызова fcntl для выставления флага fcntl. Вместо 3 системных вызова нужно использовать accept4, которая позволяет сделать тоже самое передав последним аргументом флаг SOCK_NONBLOCK за 1 системный вызов:

              int infd = accept4(sfd, &in_addr, &in_len, SOCK_NONBLOCK);
          

          aio


          Ещё 1 способ работать с IO асинхронно. В aio есть функция lio_listio, позволяющая объединить в 1 системный вызов несколько write/read, что должно уменьшить задержки на переключение в пространство ядра.

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

          epoll_wait(...., -1) -> epoll_wait(...., 0)


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

          Postscriptum


          Спасибо организаторам за конкурс, хоть всё проходило не очень гладко. Было очень увлекательно и познавательно.
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/337854/


          Метки:  

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

          Суббота, 16 Сентября 2017 г. 20:30 + в цитатник
          Jarvis7 сегодня в 20:30 Разное

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

          image

          Всем хабровчанам привет!

          Мне довелось поучаствовать в конкурсе, довольно редком в своем роде, под названием Cyber Detective. Конкурс проводился при конференции HackIT-2017, в которой также принимал активное участие. Задания основываются на поиске открытой информации в сети. Хочу поделиться опытом, райтапами и впечатлениями.

          В подобном CTF участвую впервые. Обычно участвую в CTF формата jeopardy, в которых сразу несколько категорий (Web, Reverse, Crypto, Stego, Pwn и тд). В этом же состязании разработчики сделали практически все задания на категорию Recon и одно задание на Forensic. Но об этом ниже. Изначально участвовать в соревновании не планировал, но заинтересовало задание на социальную инженерию, которое в дальнейшем придало уникальности этому состязанию и заставило играть уже серьезно.

          Задания направления Recon, или как их часто называют «задания на OSINT», решаются путем нахождения информации из открытых источников. Для решения таких заданий нужно хорошо уметь пользоваться особенностями поисковых систем, таких как Google, Duckduckgo, Shodan, Censys, знать о различных публичных базах, как правило государственных, прекрасно разбираться в особенностях социальных сетей. Разумеется, никуда без социальной инженерии. И это далеко не все умения, которыми должен владеть квалифицированный интернет-разведчик.

          image

          Всего на Cyber Detective было выложено 27 заданий, которые были разбиты на группы. Эти группы, «ветки» имели свою легенду, историю, вокруг которой крутятся задания и решения. Визуальный граф зависимости заданий, взятый с платформы, представлен выше. История раскрывается по мере решения текущей ветки, что куда интереснее, чем читать скудное условие задания на типичном CTF. К каждой истории в среднем относилось 3-4 задания.

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

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

          Intro


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

          image

          Найти было легко. Зайдя в чат телеграмма и открыв страничку чата, можно было увидеть флаг. Хочу отметить, что флаги не имеют типичного шаблона, вроде flag{...}, или md5. Это затрудняет его поиск. С другой стороны, такой формат можно разместить не везде, так что разработчики решили пожертвовать мои нервы в пользу разнообразности заданий.
          Flag: «Welcome on board!»

          Internet Profile


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

          Start


          image
          В некоторых социальных сетях есть возможность узнать информацию о странице, в частности, это vk.com, зная email/телефон пользователя, а также его фамилию. Баг это, или фича — сложно сказать, но недочет есть (на hackerone бежать репортить не стоит :) ).

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

          image
          Flag: «Orest»

          Nick


          image

          Тут все элементарно, его ник, это адрес на сайте ВК.
          Flag: «0n1zz»

          Work


          image

          Просмотрев много групп, на которые он подписан, а также бегло глянув на страницу на гитхабе его, пришла идея, что это Microsoft.
          Flag: «Microsoft»

          Profession


          image

          Решил это задание не сразу, продолжая просматривать задания ниже, и извлекая как можно больше информации со страницы, вводил такие значения как Programmer, Developer, и тд. Затем пошел искать парня в других соц. сетях, нашел его Orest Mark. При поиске, в списке профилей или в информации о странице, написано, что он Software Engineer в Microsoft.
          Flag: «Software Engineer»

          Mail


          image

          Тут было очень просто, вспомнил, что на его странице github видел мыло.
          Flag: «oreest1987@gmail.com»

          Skype


          image

          Само название задания наталкивало на то, что именно искать, ответ на Orest Mark странице.
          Flag: «orest_mark_87»

          Place-1


          image

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

          Place-2


          image

          Ответ на фейсбук странице, в графе «О себе».
          Flag: «Kiel, Germany»

          Relatives


          image

          Принимая во внимание, что у Марка есть брат, Тенсон Марк, сдаем очередной флаг.
          Flag: «Tenson Mark»

          Hobby


          image

          Уже достаточно изучив этого человека, понятно, что увлекается он гонками. Правильное название флага можно найти в списках групп на ВК странице, или, если детально приглядеться, то увидеть на аватарке его футболку, где и была эта надпись.
          Flag: «Speed racing»

          Recreation


          image

          Помня, что на ВК странице парня к посту была прикреплена геометка, погуглив место, находим решение.
          Flag: «Sesena, Spain»

          Первая ветка закрыта, задания сданы, +550 баллов буквально за 20 минут. Это дало огромный стимул разбираться дальше, наивно думая, что последующие задания будут такие же легкие. Но посмотрим в итоге, какое досье собрал на парня. Зовут его Орест Марк, знаю его страницы в VK, Facebook, Github. Знаю почту, никнейм, номер телефона. Так же то, что он любит, кем работает, где работает, где живет, где родился, кто его родственники и близкие, где они живут. Эта ветка заданий является прекрасной иллюстрацией того, как можно найти информацию о многих людях через социальные сети. Собранной информации достаточно, чтобы атаковать его почтовый адрес, попробовать получить доступ к аккаунтам в социальных сетях, применить социальную инженерию и узнать любые другие данные. Впрочем, хватит элементарных вещей, перейду к самому интересному заданию этого конкурса.

          Family Dramas


          image

          Сразу же погуглил человека по номеру, пытаться повторить трюк с восстановлением страницы в вк. Гугл выдавал что-то интересное, на первый взгляд, по запросу «0671710968», особенно первая страница. Так и не понял, что это значит. За неимением уже других вариантов решил набрать номер. К моему удивлению, ответила девушка, и сразу сбросил. Задание предполагало применение навыков социальной инженерии, как я понял в последствии, которая помогла бы выведать адрес. Нужно это было сделать не навязчиво, заинтересовав человека. Где-то 20-30 минут думал над легендой, и минут 15 ушло на то, чтобы попрактиковать чтение так, чтобы было не очень заметно, что читаю текст, говоря не навязчиво и не монотонно. Много тонкостей, которые старался учитывать. Вот текст, который составил.
          Добрый вечер!

          Меня зовут Андрей.

          Я представляю движение студентов под названием «World Frendship». Мы занимаемся тем, что объединяем людей, основываясь на взаимопомощи. Помогая в разных жизненных и бытовых вопросах, мы заводим новых знакомых, друзей, а также хотим подарить хоть немного хорошего настроения людям. Хотите поучаствовать?

          — (ответ)

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

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

          Вам же я хочу предложить поучаствовать в другом проекте под названием «Теплый ужин».
          Суть в том, что мы готовим вместе с вами еду и проводим время в веселой обстановке. Как вам предложение?

          — (ответ)

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

          — (ответ)

          Тогда нужно уточнить еще пару моментов. Подскажите, как вас зовут?

          — …

          И скажите ваш адрес
          Адрес узнать удалось. Но пришлось потом перезванивать, так как адрес немного не разобрал, и якобы сейчас уточняю по карте адрес для составления маршрута. Уже в процессе разговора понял, что подаю многовато информации, нужно было больше общения. Тем не менее, как позже признались разработчики, это была лучшая попытка из всех, кто получил и не получил адрес. До Кевина Митника мне конечно же далеко, да и кардингом не занимаюсь, но для первого раза сойдет. Хочу отметить, что именно с этого задания начал решать CTF, привлек необычный и очень интересный формат получения ответа. Думаю, админы не спали сутками, так как участников много, и решают они круглые 24 часа. Как потом мне подсказали, можно было еще в телеграмме написать этому номеру, и так же ответили бы, это сделано, думаю, для иностранцев. Хотя конечно же тут совсем не тот драйв, риск, эмоции. По телефону нужно отвечать быстро на вопросы, которые не мог предусмотреть, и качество ответов зависело от грамотности и продуманности легенды. Получив классный опыт, новые впечатления еще больше хотел продолжать решать. Однако такое интересное задание оценивалось в 100 очков, что не порадовало, не справедливо выделили баллы для этого задания на мой взгляд. До игроков в топе скорборда было далеко, но две бессонные ночи это исправили.
          Flag: «Odessa, Palubnaya, 7»

          Retribution


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

          Step #1


          image

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

          image

          Вот такой сайт открылся, и получил первый флаг.

          image
          Flag: «HACK IN DARK»

          Step #2


          image

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

          image

          Кликнув на одну из услуг получаю QR код.

          image

          Расшифровка: DRYcucyK5Hfc3A4hit9KqsKm5FwxHJYSdk

          За время CTF эти QR коды менялись, что усложняло решение, немного сбивая с толку. Один из таких кодов успешно расшифровывался через base64 decoder, и получив довольно непонятный текст, пошел по ложному пути, разбирая что это такое. Потом досмотрел, что внизу страницы есть упоминание такой вещи как dogecoin, и сразу стало ясно, что это криптовалюта. Сейчас, когда пишу это описание, в принципе, это кажется очевидным, но на тот момент так не казалось. Погуглив, узнал, что dogecoin — это альткоин, построенный на основе блокчейна. Зайдя на сайт Dogecoin зарегистрировал кошелек, пытался с ним разобраться. Но все оказалось проще, нужно было просмотреть историю транзакций для текущего кошелька. Вот такой список переводов для этого кошелька.

          image

          С криптовалютами сталкивался, но отслеживать операции не приходилось, и это оказалось для меня проблемой. Здесь пересылка монет осуществляется через промежуточные кошельки, которые пересылают эти монеты через другие кошельки и так далее. Всю эту схему можно отследить через программу Maltego, входящую в состав KaliLinux и Parrot OS, а также можно было воспользоваться online сервисами, которые смогли бы это сделать автоматизировано. В процессе решения же делал по довольно простому способу — кликал на транзакции с уходящими монетами из кошелька, выбирая наибольшую сумму. В результате нескольких переходов нашел кошелек, куда стекались монеты. Конечно же сильно усложнили бы работу различные миксеры, но разработчики не стали усложнять и без того тяжелую жизнь участников.
          Flag: «DMqh6vFJ5LpdEbJnW5NYhwRmW5tAC69UmG»

          Step #3


          image

          Это задание оказалось очень простым, за несколько минут сделал. Нужно было погуглить кошелек, его упоминали на нескольких форумах, вот тот форум, который привлек мое внимание благодаря одному положительному отзыву.
          Flag: «w3bg00dua»

          Step #4


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

          image

          Заинтересовал аккаунт на Github, в котором был один репозиторий, и внизу описания был email для фидбеков или предложений.

          image

          С емейлом тоже долго морочался, никуда он не подходил. Спустя время догадался ввести в поиске google+, где и получил ссылку на аккаунт некого GlebReed.
          Flag: «Gleb Reed»

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

          Internet Fraud


          image

          Step #1


          image

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

          image

          Просканировал сайт на поддиректории утилитой dirsearch, и стало ясно, что сайт работает на Wordpress. Конечно же по привычке начал сканировать wpscan-ом, и прочими утилитами, искать уязвимые плагины, но этого делать не стоило.

          image

          Захожу на страницу авторизации, нажимаю «Lost your password?» и попадаю на страницу сайта.

          image

          Заходя на некоторые страницы меня перенаправляло на дефолтную страницу, которую видел при открытии сайта. Впрочем, на сайте ничего не нашел и начал гуглить. Запрос «site:shop.cyber-detective.hackit.ua» ничего не дал. Но зайдя на ресурс Web Archive, нашел много интересного. Это сервис позволяет хранить снимки сайтов. Даже если сайт будет удален, есть возможность просмотреть html страницы и некоторые картинки. Так же есть возможность делать снимки сайта самостоятельно. Есть снимки за 2 дня.

          image

          В снимках за 3-е число ничего интересного, а вот за 5-е число на странице Contacts есть емейл и скайп.

          image

          Стоит обратить внимание на то, что здесь кривая верстка. Нарочно это сделано, или webarchive сделал это сам, не известно, но зайдя в инспектор элементов, можно увидеть, что емейл отделен тегами. Впрочем, этот емейл ошибочный, нужно продолжать искать. Поиск скайпа ничего не дал, как и поиск по никнеймам. Ответ кроется настранице The best clothes wholesale in UkraineвфайлеWomen’s clothes Spring 2017. Внутри файла ничего интересно, но в свойствах был автор, никнейм которого и являлся частью необходимой почты.

          image
          Flag:«salesmanager@shop.cyber-detective.hackit.ua»

          Step #2

          image
          Сразу понятно, что тут флагом является IP адрес. Скорее всего IP получателя письма. Отправил письмо на этот емейл. Ответа не последовало. Нужно сказать, что отправляя письмо на info@shop.cyber-detective.hackit.ua, приходило письмо с сообщением, что оно не доставлено. Так как ответа на мое последнее письмо не последовало, стал составлять сниффер для перехвата IP адреса того, кто его открывает. Вот такой сниффер у меня вышел.

          
          
          
          
          Добрый вечер!
          Почему не работает сайт shop.cyber-detective.hackit.ua? 
          
          
          

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

          image

          Смотрим подробную информацию о письме.

          image

          image
          Flag:«195.64.154.110»

          Step #3


          image

          Имея небольшой опыт в поиске по фамилиям, судя по полному имени человека, еще и большим регистром записано, предположил, что его найти можно в каком-то государственном реестре.
          Миньюст прекрасно с этим справился. Ввожу ФИО, получаю трех людей. Опытным путем определяю, что человек с местонахождением регистрационного дела «Департамент реєстрації Харківської міської ради» подходит.

          image
          Flag:«80577109515»

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

          OLX Fraud


          image

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

          Step #1


          image

          Тут начал уже составлять досье со всей информацией, которую нашел где-либо. Что знаю? Парня зовут Игорь, номер телефона 380983607320, живет в районе Червонозаводской, в Харькове, а ферма у него вот такая «Майнинг ферма AMD BOX 6 GPU RX 580 180мх/с». Нашел парня в телеграмме, написал ему, звонил, но никто не ответил, и тогда сильно в сторону пошел, как потом понял. Сначала попытался отсеять в ВК тех, кого зовут Игорь, их приблизительно 2 миллиона. Выбрал мужской пол, Украина, Харьков, получилось где-то 22 тысячи. Возможно и получилось бы найти что-то, если бы фамилия была более экзотической. Отсеять из 22 тысяч кого-то не вышло. Гугление картинки ничего не дало, а вот гугление названия фермы дало кое-что интересное. Наткнулся на объявления на других сайтах, вот например это. Я сразу подумал, что правильно решаю, так как фотографии идентичные. Продолжил искать и наткнулся на объявление в ВК, не так давно стало возможно продавать товар там. Нашел страницу парняи потратил огромное количество времени, на то, что бы узнать близких этого человека. Это был ложный след. Вернувшись к этому заданию после сдачи некоторых других, продолжил разбираться с телеграмм аккаунтом, снова писал и звонил, и как в прошлый раз — никто не ответил. Еще спустя время смог понять, как узнать никнейм в телеграмме, зная номер телефона. И это было верное направление. В телеграмме добавил контакт, результат ниже. Можно попробовать что-то написать, но не ответят.

          image image

          Затем нужно зайти в аккаунт, как на первой картинке, и в верхнем правом меню нажать кнопку «Удалить». Пара секунд и получится вот так.

          image

          Смысл в том, что когда удаляем этот контакт, то приложение не выбрасывает в список диалогов, и контакт не исчезает (куда, например, тогда девать переписку), и разработчики телеграмма решили номер\имя заменять на никнейм (и снова не надо бежать на hackerone строчить репорт). Никнейм — Gh0stbust3rs. Беру ранее упомянутый сервис. Здесь снова было огромное количество неверных ходов, так как аккаунтов с таким ником много. Верным решением было выбрать страницу ВК этого человека — Эд Высоцкий. Тут нужно зайти в группы, которые он подписан и сразу бросилась группа с малым количеством подписчиков и странным названием.

          image

          Администратор этого паблика Анна Гостева, которая имеет семейное положение с Эдом Высоцким, значит это наша цель.
          Flag: «04.06.1991»

          Step #2


          image

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

          image

          image

          Нахожу у него фото с геометкой, некий жилой комплекс.

          image
          Flag:«ЖК „5th Avenue“» (в флаге стандартные двойные кавычки)

          Хочется отметить, что сложность ветки, главным образом, заключается в том, что мошенник взял фото\материалы из интернета и это сильно усложнило поиски. Стоило за эту ветку дать больше, чем +300 очков. Но в любом случае это не самая сложная ветка.

          Facke accounts


          image

          Step #1


          image

          Эта ветка показалась самой сложной. Расспрашивая о верном решении у разработчиков понял, что там все гораздо проще, но делается через способ, известный очень немногим. Впрочем, разберу сначала ложные пути, затем то, как сделал и как нужно было. Есть Наталья Афян. Нужно найти ресторан, запомнил. Детально изучаю страницу этого фейка. Вот фотография, которая выдает владельца оригинальной страницы. Настоящий человек это Ольга Дьякова, которая работает в Slow Food Kiev. Чувствуя, что это не с проста, детально анализирую ее страницу, это никак не помогает. Нахожу несколько статей о ней, вот одна из них, с которой, кстати, были взяты почти все фотографии для фейкового аккаунта. Изучение всего этого материала ничего не дало. Затем стал анализировать лайки, которые стоят под некоторыми постами на стене. Ничего особенного не заметил. Репосты делались на стену из популярных пабликов, тоже искать там не вариант. Оставалось анализировать друзей фейка, которых у нее 133. Под свежую чашку кофе стал открывать каждую страницу. Людей, у которых страница оформлена не на русском, или английском языке пропускал. В результате выделил такую страницу — Mariya Odintsova. Училась в ХНУРЭ, из Харькова и привлекли еще посты на стене, есть два похожих с постами Натальи Афян. Изучая репостыэтого и этого постов, изучая тех, кто делал репост, вижу следующее.

          image

          Наталью Афян и Mariya Odintsova уже знаю, вот кто первые два человека не понятно. Владимир Кулаковский был случайным пользователем, а вот Ольга Пирунова оказалась очень похожей на фейк. Проверил — в друзьях Натальи Афян она имеется. Что же получается? Человек зарегистрировал не один, а три фейковых аккаунта, добавил в каждый аккаунт много друзей и другие свои же созданные фейки. Более того, сделал на всех трех страницах репост 2х одинаковых записей. Это странно, но видя, что это искусственно создавали, понимаю, что продвигаюсь. Вот тут опять застрял, так как ни на одной странице не было каких-либо полезных отсылок к кому-либо еще. Спустя несколько часов изучения друзей понял, что как минимум один аккаунт встречается в друзьях всех трех фейков — Oleg Stanov. На его странице есть пост, на фото которого видно именную салфетницу ресторана.

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

          image

          Теперь о том, как стоило решать. Некоторые вышеупомянутые фейки лайкали вот этот и этот посты Натальи Афян. Среди лайков встретился и Oleg Stanov. Но пока не понятно, что именно он центр всего этого дела, нужно продолжать собирать информацию. Зайдя к тем, кто лайкал Наталью Афян, можно выделить Mariya Odintsova по способу, описанному выше. Теперь воспользуемся фишкой Фейсбука, которой нет в ВК. Нужно зайти на страницу восстановления пароля, ввести имя и фамилию.

          image

          image

          image

          image

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

          image
          Flag:«Stargorod»

          Step #2


          image

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

          image
          Flag:«Oleg Sotnichuk»

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

          Business and black bookkeeping


          image

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

          Step #1


          image

          Вот такой сайт открылся.

          image

          Прежде чем выйти на правильный след, наткнулся на несколько ложных. Например было несколько сайтов, внешне похожих по интерфейсу, например этот. Так же гугл выдает закешированные страницы, которых на момент прохождения CTF уже не было. Запрос в гугле был site:company.cyber-detective.hackit.ua.

          image

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

          image

          Полученное облако.

          image
          Flag:«cloud.company.cyber-detective.hackit.ua»

          Step #2


          image

          Это задание было самым веселым среди всех, выполнял его в кафе в прикуску с пиццей, и 2 часа хорошо подобранных песен были очень кстати. Решение очевидное, так как это не стандартный CTF и никакой сложной стеганографии здесь не должно быть. Между песнями был вставлен кусок разговора директора с подчиненной, в ходе которого подчиненная сообщила важную информацию. Что бы найти песню, достаточно Проигрывателя Windows Media, понемногу пролистывая музыкальную дорожку вперед. Для ценителей софта скрин ниже. Разговор длился 15 секунд.

          image
          Логин: director
          Пароль: Vladimir-1985

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

          image

          Сразу привлекла картинка с названием Paris.jpg, но флаг не подошел. В папке Photos From Holidays были такие картинки, которые, на мой взгляд слишком круты для фотографий для обычного человека. Возможно это как картинки-воспоминания для человека, который там побывал.

          image

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

          image
          Flag:«San Francisco»

          Step #3


          image

          В прилепленных фотографиях были чексуммы для файлов, рассчитаны через программу 7-Zip.
          Данный таск был из категории Forensic. В облаке был файл «Private Files.tc», а в задании предлагалось скачать «20170906.mem». Первый файл был TrueCrypt контейнером, второй — дампом памяти, в котором спрятаны сессионные ключи от контейнера. Для решения перепробовал такие утилиты как pytruecrypt (для расшифровки с помощью добытых ключей), volatility, Elcomsoft Forensi Disk Decryptor. Подошла программа Passware Kit Forensic, но расшифровать удалось не сразу. Под этот криптоконтейнер подошла Passware Kit Forensic только определенной версии.

          image

          Процесс расшифровки прост, меню «Full Disk Encryption»->«TrueCrypt», далее указываются пути к контейнеру и дампу памяти. Далее процесс расшифровки.

          image

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

          image

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

          image
          Flag:«26,542,579,522.00»

          Мне довелось поучаствовать в уникальном CTF, в котором было огромное количество нестандартных заданий, встречающихся мне впервые. Успешно вышло занять третье место, хотя, имея больше предварительного опыта, смог бы достичь большего. Огромные молодцы победители, вместе боролись за победу многие часы на пролет без сна. Порадовало очень задание на социальную инженерию. Были и не очень приятные задания, но это как обычно на любом CTF. Опыт получил просто колоссальный. Статью старался писать максимально подробно, дабы любой интересующийся по шагам смог повторить мои действия, и более того, понять те ложные пути и ошибки, которые довелось совершить мне.Это поможет гораздо быстрее решать подобные задания в будущем. Задания доступны на сайте еще неделю, дерзайте! Всем же остальным советую быть бдительными, в 21 веке цифровой след — это неотъемлемая часть жизни, и нужно думать, что оставлять в сети.

          Уважаемый читатель, если ты дошел до этого места, поздравляю! Надеюсь ты извлек что то полезное из этой статьи. Если есть вопросы — велкам в ЛС или комментарии.
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338078/


          Метки:  

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

          Суббота, 16 Сентября 2017 г. 20:30 + в цитатник
          Jarvis7 сегодня в 20:30 Разное

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

          image

          Всем хабровчанам привет!

          Мне довелось поучаствовать в конкурсе, довольно редком в своем роде, под названием Cyber Detective. Конкурс проводился при конференции HackIT-2017, в которой также принимал активное участие. Задания основываются на поиске открытой информации в сети. Хочу поделиться опытом, райтапами и впечатлениями.

          В подобном CTF участвую впервые. Обычно участвую в CTF формата jeopardy, в которых сразу несколько категорий (Web, Reverse, Crypto, Stego, Pwn и тд). В этом же состязании разработчики сделали практически все задания на категорию Recon и одно задание на Forensic. Но об этом ниже. Изначально участвовать в соревновании не планировал, но заинтересовало задание на социальную инженерию, которое в дальнейшем придало уникальности этому состязанию и заставило играть уже серьезно.

          Задания направления Recon, или как их часто называют «задания на OSINT», решаются путем нахождения информации из открытых источников. Для решения таких заданий нужно хорошо уметь пользоваться особенностями поисковых систем, таких как Google, Duckduckgo, Shodan, Censys, знать о различных публичных базах, как правило государственных, прекрасно разбираться в особенностях социальных сетей. Разумеется, никуда без социальной инженерии. И это далеко не все умения, которыми должен владеть квалифицированный интернет-разведчик.

          image

          Всего на Cyber Detective было выложено 27 заданий, которые были разбиты на группы. Эти группы, «ветки» имели свою легенду, историю, вокруг которой крутятся задания и решения. Визуальный граф зависимости заданий, взятый с платформы, представлен выше. История раскрывается по мере решения текущей ветки, что куда интереснее, чем читать скудное условие задания на типичном CTF. К каждой истории в среднем относилось 3-4 задания.

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

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

          Intro


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

          image

          Найти было легко. Зайдя в чат телеграмма и открыв страничку чата, можно было увидеть флаг. Хочу отметить, что флаги не имеют типичного шаблона, вроде flag{...}, или md5. Это затрудняет его поиск. С другой стороны, такой формат можно разместить не везде, так что разработчики решили пожертвовать мои нервы в пользу разнообразности заданий.
          Flag: «Welcome on board!»

          Internet Profile


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

          Start


          image
          В некоторых социальных сетях есть возможность узнать информацию о странице, в частности, это vk.com, зная email/телефон пользователя, а также его фамилию. Баг это, или фича — сложно сказать, но недочет есть (на hackerone бежать репортить не стоит :) ).

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

          image
          Flag: «Orest»

          Nick


          image

          Тут все элементарно, его ник, это адрес на сайте ВК.
          Flag: «0n1zz»

          Work


          image

          Просмотрев много групп, на которые он подписан, а также бегло глянув на страницу на гитхабе его, пришла идея, что это Microsoft.
          Flag: «Microsoft»

          Profession


          image

          Решил это задание не сразу, продолжая просматривать задания ниже, и извлекая как можно больше информации со страницы, вводил такие значения как Programmer, Developer, и тд. Затем пошел искать парня в других соц. сетях, нашел его Orest Mark. При поиске, в списке профилей или в информации о странице, написано, что он Software Engineer в Microsoft.
          Flag: «Software Engineer»

          Mail


          image

          Тут было очень просто, вспомнил, что на его странице github видел мыло.
          Flag: «oreest1987@gmail.com»

          Skype


          image

          Само название задания наталкивало на то, что именно искать, ответ на Orest Mark странице.
          Flag: «orest_mark_87»

          Place-1


          image

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

          Place-2


          image

          Ответ на фейсбук странице, в графе «О себе».
          Flag: «Kiel, Germany»

          Relatives


          image

          Принимая во внимание, что у Марка есть брат, Тенсон Марк, сдаем очередной флаг.
          Flag: «Tenson Mark»

          Hobby


          image

          Уже достаточно изучив этого человека, понятно, что увлекается он гонками. Правильное название флага можно найти в списках групп на ВК странице, или, если детально приглядеться, то увидеть на аватарке его футболку, где и была эта надпись.
          Flag: «Speed racing»

          Recreation


          image

          Помня, что на ВК странице парня к посту была прикреплена геометка, погуглив место, находим решение.
          Flag: «Sesena, Spain»

          Первая ветка закрыта, задания сданы, +550 баллов буквально за 20 минут. Это дало огромный стимул разбираться дальше, наивно думая, что последующие задания будут такие же легкие. Но посмотрим в итоге, какое досье собрал на парня. Зовут его Орест Марк, знаю его страницы в VK, Facebook, Github. Знаю почту, никнейм, номер телефона. Так же то, что он любит, кем работает, где работает, где живет, где родился, кто его родственники и близкие, где они живут. Эта ветка заданий является прекрасной иллюстрацией того, как можно найти информацию о многих людях через социальные сети. Собранной информации достаточно, чтобы атаковать его почтовый адрес, попробовать получить доступ к аккаунтам в социальных сетях, применить социальную инженерию и узнать любые другие данные. Впрочем, хватит элементарных вещей, перейду к самому интересному заданию этого конкурса.

          Family Dramas


          image

          Сразу же погуглил человека по номеру, пытаться повторить трюк с восстановлением страницы в вк. Гугл выдавал что-то интересное, на первый взгляд, по запросу «0671710968», особенно первая страница. Так и не понял, что это значит. За неимением уже других вариантов решил набрать номер. К моему удивлению, ответила девушка, и сразу сбросил. Задание предполагало применение навыков социальной инженерии, как я понял в последствии, которая помогла бы выведать адрес. Нужно это было сделать не навязчиво, заинтересовав человека. Где-то 20-30 минут думал над легендой, и минут 15 ушло на то, чтобы попрактиковать чтение так, чтобы было не очень заметно, что читаю текст, говоря не навязчиво и не монотонно. Много тонкостей, которые старался учитывать. Вот текст, который составил.
          Добрый вечер!

          Меня зовут Андрей.

          Я представляю движение студентов под названием «World Frendship». Мы занимаемся тем, что объединяем людей, основываясь на взаимопомощи. Помогая в разных жизненных и бытовых вопросах, мы заводим новых знакомых, друзей, а также хотим подарить хоть немного хорошего настроения людям. Хотите поучаствовать?

          — (ответ)

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

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

          Вам же я хочу предложить поучаствовать в другом проекте под названием «Теплый ужин».
          Суть в том, что мы готовим вместе с вами еду и проводим время в веселой обстановке. Как вам предложение?

          — (ответ)

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

          — (ответ)

          Тогда нужно уточнить еще пару моментов. Подскажите, как вас зовут?

          — …

          И скажите ваш адрес
          Адрес узнать удалось. Но пришлось потом перезванивать, так как адрес немного не разобрал, и якобы сейчас уточняю по карте адрес для составления маршрута. Уже в процессе разговора понял, что подаю многовато информации, нужно было больше общения. Тем не менее, как позже признались разработчики, это была лучшая попытка из всех, кто получил и не получил адрес. До Кевина Митника мне конечно же далеко, да и кардингом не занимаюсь, но для первого раза сойдет. Хочу отметить, что именно с этого задания начал решать CTF, привлек необычный и очень интересный формат получения ответа. Думаю, админы не спали сутками, так как участников много, и решают они круглые 24 часа. Как потом мне подсказали, можно было еще в телеграмме написать этому номеру, и так же ответили бы, это сделано, думаю, для иностранцев. Хотя конечно же тут совсем не тот драйв, риск, эмоции. По телефону нужно отвечать быстро на вопросы, которые не мог предусмотреть, и качество ответов зависело от грамотности и продуманности легенды. Получив классный опыт, новые впечатления еще больше хотел продолжать решать. Однако такое интересное задание оценивалось в 100 очков, что не порадовало, не справедливо выделили баллы для этого задания на мой взгляд. До игроков в топе скорборда было далеко, но две бессонные ночи это исправили.
          Flag: «Odessa, Palubnaya, 7»

          Retribution


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

          Step #1


          image

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

          image

          Вот такой сайт открылся, и получил первый флаг.

          image
          Flag: «HACK IN DARK»

          Step #2


          image

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

          image

          Кликнув на одну из услуг получаю QR код.

          image

          Расшифровка: DRYcucyK5Hfc3A4hit9KqsKm5FwxHJYSdk

          За время CTF эти QR коды менялись, что усложняло решение, немного сбивая с толку. Один из таких кодов успешно расшифровывался через base64 decoder, и получив довольно непонятный текст, пошел по ложному пути, разбирая что это такое. Потом досмотрел, что внизу страницы есть упоминание такой вещи как dogecoin, и сразу стало ясно, что это криптовалюта. Сейчас, когда пишу это описание, в принципе, это кажется очевидным, но на тот момент так не казалось. Погуглив, узнал, что dogecoin — это альткоин, построенный на основе блокчейна. Зайдя на сайт Dogecoin зарегистрировал кошелек, пытался с ним разобраться. Но все оказалось проще, нужно было просмотреть историю транзакций для текущего кошелька. Вот такой список переводов для этого кошелька.

          image

          С криптовалютами сталкивался, но отслеживать операции не приходилось, и это оказалось для меня проблемой. Здесь пересылка монет осуществляется через промежуточные кошельки, которые пересылают эти монеты через другие кошельки и так далее. Всю эту схему можно отследить через программу Maltego, входящую в состав KaliLinux и Parrot OS, а также можно было воспользоваться online сервисами, которые смогли бы это сделать автоматизировано. В процессе решения же делал по довольно простому способу — кликал на транзакции с уходящими монетами из кошелька, выбирая наибольшую сумму. В результате нескольких переходов нашел кошелек, куда стекались монеты. Конечно же сильно усложнили бы работу различные миксеры, но разработчики не стали усложнять и без того тяжелую жизнь участников.
          Flag: «DMqh6vFJ5LpdEbJnW5NYhwRmW5tAC69UmG»

          Step #3


          image

          Это задание оказалось очень простым, за несколько минут сделал. Нужно было погуглить кошелек, его упоминали на нескольких форумах, вот тот форум, который привлек мое внимание благодаря одному положительному отзыву.
          Flag: «w3bg00dua»

          Step #4


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

          image

          Заинтересовал аккаунт на Github, в котором был один репозиторий, и внизу описания был email для фидбеков или предложений.

          image

          С емейлом тоже долго морочался, никуда он не подходил. Спустя время догадался ввести в поиске google+, где и получил ссылку на аккаунт некого GlebReed.
          Flag: «Gleb Reed»

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

          Internet Fraud


          image

          Step #1


          image

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

          image

          Просканировал сайт на поддиректории утилитой dirsearch, и стало ясно, что сайт работает на Wordpress. Конечно же по привычке начал сканировать wpscan-ом, и прочими утилитами, искать уязвимые плагины, но этого делать не стоило.

          image

          Захожу на страницу авторизации, нажимаю «Lost your password?» и попадаю на страницу сайта.

          image

          Заходя на некоторые страницы меня перенаправляло на дефолтную страницу, которую видел при открытии сайта. Впрочем, на сайте ничего не нашел и начал гуглить. Запрос «site:shop.cyber-detective.hackit.ua» ничего не дал. Но зайдя на ресурс Web Archive, нашел много интересного. Это сервис позволяет хранить снимки сайтов. Даже если сайт будет удален, есть возможность просмотреть html страницы и некоторые картинки. Так же есть возможность делать снимки сайта самостоятельно. Есть снимки за 2 дня.

          image

          В снимках за 3-е число ничего интересного, а вот за 5-е число на странице Contacts есть емейл и скайп.

          image

          Стоит обратить внимание на то, что здесь кривая верстка. Нарочно это сделано, или webarchive сделал это сам, не известно, но зайдя в инспектор элементов, можно увидеть, что емейл отделен тегами. Впрочем, этот емейл ошибочный, нужно продолжать искать. Поиск скайпа ничего не дал, как и поиск по никнеймам. Ответ кроется настранице The best clothes wholesale in UkraineвфайлеWomen’s clothes Spring 2017. Внутри файла ничего интересно, но в свойствах был автор, никнейм которого и являлся частью необходимой почты.

          image
          Flag:«salesmanager@shop.cyber-detective.hackit.ua»

          Step #2

          image
          Сразу понятно, что тут флагом является IP адрес. Скорее всего IP получателя письма. Отправил письмо на этот емейл. Ответа не последовало. Нужно сказать, что отправляя письмо на info@shop.cyber-detective.hackit.ua, приходило письмо с сообщением, что оно не доставлено. Так как ответа на мое последнее письмо не последовало, стал составлять сниффер для перехвата IP адреса того, кто его открывает. Вот такой сниффер у меня вышел.

          
          
          
          
          Добрый вечер!
          Почему не работает сайт shop.cyber-detective.hackit.ua? 
          
          
          

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

          image

          Смотрим подробную информацию о письме.

          image

          image
          Flag:«195.64.154.110»

          Step #3


          image

          Имея небольшой опыт в поиске по фамилиям, судя по полному имени человека, еще и большим регистром записано, предположил, что его найти можно в каком-то государственном реестре.
          Миньюст прекрасно с этим справился. Ввожу ФИО, получаю трех людей. Опытным путем определяю, что человек с местонахождением регистрационного дела «Департамент реєстрації Харківської міської ради» подходит.

          image
          Flag:«80577109515»

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

          OLX Fraud


          image

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

          Step #1


          image

          Тут начал уже составлять досье со всей информацией, которую нашел где-либо. Что знаю? Парня зовут Игорь, номер телефона 380983607320, живет в районе Червонозаводской, в Харькове, а ферма у него вот такая «Майнинг ферма AMD BOX 6 GPU RX 580 180мх/с». Нашел парня в телеграмме, написал ему, звонил, но никто не ответил, и тогда сильно в сторону пошел, как потом понял. Сначала попытался отсеять в ВК тех, кого зовут Игорь, их приблизительно 2 миллиона. Выбрал мужской пол, Украина, Харьков, получилось где-то 22 тысячи. Возможно и получилось бы найти что-то, если бы фамилия была более экзотической. Отсеять из 22 тысяч кого-то не вышло. Гугление картинки ничего не дало, а вот гугление названия фермы дало кое-что интересное. Наткнулся на объявления на других сайтах, вот например это. Я сразу подумал, что правильно решаю, так как фотографии идентичные. Продолжил искать и наткнулся на объявление в ВК, не так давно стало возможно продавать товар там. Нашел страницу парняи потратил огромное количество времени, на то, что бы узнать близких этого человека. Это был ложный след. Вернувшись к этому заданию после сдачи некоторых других, продолжил разбираться с телеграмм аккаунтом, снова писал и звонил, и как в прошлый раз — никто не ответил. Еще спустя время смог понять, как узнать никнейм в телеграмме, зная номер телефона. И это было верное направление. В телеграмме добавил контакт, результат ниже. Можно попробовать что-то написать, но не ответят.

          image image

          Затем нужно зайти в аккаунт, как на первой картинке, и в верхнем правом меню нажать кнопку «Удалить». Пара секунд и получится вот так.

          image

          Смысл в том, что когда удаляем этот контакт, то приложение не выбрасывает в список диалогов, и контакт не исчезает (куда, например, тогда девать переписку), и разработчики телеграмма решили номер\имя заменять на никнейм (и снова не надо бежать на hackerone строчить репорт). Никнейм — Gh0stbust3rs. Беру ранее упомянутый сервис. Здесь снова было огромное количество неверных ходов, так как аккаунтов с таким ником много. Верным решением было выбрать страницу ВК этого человека — Эд Высоцкий. Тут нужно зайти в группы, которые он подписан и сразу бросилась группа с малым количеством подписчиков и странным названием.

          image

          Администратор этого паблика Анна Гостева, которая имеет семейное положение с Эдом Высоцким, значит это наша цель.
          Flag: «04.06.1991»

          Step #2


          image

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

          image

          image

          Нахожу у него фото с геометкой, некий жилой комплекс.

          image
          Flag:«ЖК „5th Avenue“» (в флаге стандартные двойные кавычки)

          Хочется отметить, что сложность ветки, главным образом, заключается в том, что мошенник взял фото\материалы из интернета и это сильно усложнило поиски. Стоило за эту ветку дать больше, чем +300 очков. Но в любом случае это не самая сложная ветка.

          Facke accounts


          image

          Step #1


          image

          Эта ветка показалась самой сложной. Расспрашивая о верном решении у разработчиков понял, что там все гораздо проще, но делается через способ, известный очень немногим. Впрочем, разберу сначала ложные пути, затем то, как сделал и как нужно было. Есть Наталья Афян. Нужно найти ресторан, запомнил. Детально изучаю страницу этого фейка. Вот фотография, которая выдает владельца оригинальной страницы. Настоящий человек это Ольга Дьякова, которая работает в Slow Food Kiev. Чувствуя, что это не с проста, детально анализирую ее страницу, это никак не помогает. Нахожу несколько статей о ней, вот одна из них, с которой, кстати, были взяты почти все фотографии для фейкового аккаунта. Изучение всего этого материала ничего не дало. Затем стал анализировать лайки, которые стоят под некоторыми постами на стене. Ничего особенного не заметил. Репосты делались на стену из популярных пабликов, тоже искать там не вариант. Оставалось анализировать друзей фейка, которых у нее 133. Под свежую чашку кофе стал открывать каждую страницу. Людей, у которых страница оформлена не на русском, или английском языке пропускал. В результате выделил такую страницу — Mariya Odintsova. Училась в ХНУРЭ, из Харькова и привлекли еще посты на стене, есть два похожих с постами Натальи Афян. Изучая репостыэтого и этого постов, изучая тех, кто делал репост, вижу следующее.

          image

          Наталью Афян и Mariya Odintsova уже знаю, вот кто первые два человека не понятно. Владимир Кулаковский был случайным пользователем, а вот Ольга Пирунова оказалась очень похожей на фейк. Проверил — в друзьях Натальи Афян она имеется. Что же получается? Человек зарегистрировал не один, а три фейковых аккаунта, добавил в каждый аккаунт много друзей и другие свои же созданные фейки. Более того, сделал на всех трех страницах репост 2х одинаковых записей. Это странно, но видя, что это искусственно создавали, понимаю, что продвигаюсь. Вот тут опять застрял, так как ни на одной странице не было каких-либо полезных отсылок к кому-либо еще. Спустя несколько часов изучения друзей понял, что как минимум один аккаунт встречается в друзьях всех трех фейков — Oleg Stanov. На его странице есть пост, на фото которого видно именную салфетницу ресторана.

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

          image

          Теперь о том, как стоило решать. Некоторые вышеупомянутые фейки лайкали вот этот и этот посты Натальи Афян. Среди лайков встретился и Oleg Stanov. Но пока не понятно, что именно он центр всего этого дела, нужно продолжать собирать информацию. Зайдя к тем, кто лайкал Наталью Афян, можно выделить Mariya Odintsova по способу, описанному выше. Теперь воспользуемся фишкой Фейсбука, которой нет в ВК. Нужно зайти на страницу восстановления пароля, ввести имя и фамилию.

          image

          image

          image

          image

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

          image
          Flag:«Stargorod»

          Step #2


          image

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

          image
          Flag:«Oleg Sotnichuk»

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

          Business and black bookkeeping


          image

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

          Step #1


          image

          Вот такой сайт открылся.

          image

          Прежде чем выйти на правильный след, наткнулся на несколько ложных. Например было несколько сайтов, внешне похожих по интерфейсу, например этот. Так же гугл выдает закешированные страницы, которых на момент прохождения CTF уже не было. Запрос в гугле был site:company.cyber-detective.hackit.ua.

          image

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

          image

          Полученное облако.

          image
          Flag:«cloud.company.cyber-detective.hackit.ua»

          Step #2


          image

          Это задание было самым веселым среди всех, выполнял его в кафе в прикуску с пиццей, и 2 часа хорошо подобранных песен были очень кстати. Решение очевидное, так как это не стандартный CTF и никакой сложной стеганографии здесь не должно быть. Между песнями был вставлен кусок разговора директора с подчиненной, в ходе которого подчиненная сообщила важную информацию. Что бы найти песню, достаточно Проигрывателя Windows Media, понемногу пролистывая музыкальную дорожку вперед. Для ценителей софта скрин ниже. Разговор длился 15 секунд.

          image
          Логин: director
          Пароль: Vladimir-1985

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

          image

          Сразу привлекла картинка с названием Paris.jpg, но флаг не подошел. В папке Photos From Holidays были такие картинки, которые, на мой взгляд слишком круты для фотографий для обычного человека. Возможно это как картинки-воспоминания для человека, который там побывал.

          image

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

          image
          Flag:«San Francisco»

          Step #3


          image

          В прилепленных фотографиях были чексуммы для файлов, рассчитаны через программу 7-Zip.
          Данный таск был из категории Forensic. В облаке был файл «Private Files.tc», а в задании предлагалось скачать «20170906.mem». Первый файл был TrueCrypt контейнером, второй — дампом памяти, в котором спрятаны сессионные ключи от контейнера. Для решения перепробовал такие утилиты как pytruecrypt (для расшифровки с помощью добытых ключей), volatility, Elcomsoft Forensi Disk Decryptor. Подошла программа Passware Kit Forensic, но расшифровать удалось не сразу. Под этот криптоконтейнер подошла Passware Kit Forensic только определенной версии.

          image

          Процесс расшифровки прост, меню «Full Disk Encryption»->«TrueCrypt», далее указываются пути к контейнеру и дампу памяти. Далее процесс расшифровки.

          image

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

          image

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

          image
          Flag:«26,542,579,522.00»

          Мне довелось поучаствовать в уникальном CTF, в котором было огромное количество нестандартных заданий, встречающихся мне впервые. Успешно вышло занять третье место, хотя, имея больше предварительного опыта, смог бы достичь большего. Огромные молодцы победители, вместе боролись за победу многие часы на пролет без сна. Порадовало очень задание на социальную инженерию. Были и не очень приятные задания, но это как обычно на любом CTF. Опыт получил просто колоссальный. Статью старался писать максимально подробно, дабы любой интересующийся по шагам смог повторить мои действия, и более того, понять те ложные пути и ошибки, которые довелось совершить мне.Это поможет гораздо быстрее решать подобные задания в будущем. Задания доступны на сайте еще неделю, дерзайте! Всем же остальным советую быть бдительными, в 21 веке цифровой след — это неотъемлемая часть жизни, и нужно думать, что оставлять в сети.

          Уважаемый читатель, если ты дошел до этого места, поздравляю! Надеюсь ты извлек что то полезное из этой статьи. Если есть вопросы — велкам в ЛС или комментарии.
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338078/


          Метки:  

          [Перевод] Моки и явные контракты

          Суббота, 16 Сентября 2017 г. 19:08 + в цитатник
          Telichkin сегодня в 19:08 Разработка

          Моки и явные контракты

          • Перевод

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


          Ниже представлен вольный перевод статьи, в которой Jos'e Valim — создатель языка Elixir — высказал своё мнение на проблему использования моков, с которым я полностью согласен.




          Несколько дней назад я поделился своими мыслями по поводу моков в Twitter:



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


          Что такое мок?


          Воспользуемся определением из англоязычной википедии: мок — настраиваемый объект, который имитирует поведение реального объекта. Я сделаю акцент на этом позже, но для меня мок — это всегда существительное, а не глагол [для наглядности, глагол mock везде будет переводиться как "замокать" — прим. перев.].


          На примере внешнего API


          Давайте рассмотрим стандартный пример из реальной жизни: внешнее API.


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


          defmodule MyApp.MyController do
              def show(conn, %{"username" => username}) do
                  # ...
                  MyApp.TwitterClient.get_username(username)
                  # ...
              end
          end

          Стандартным подходом при тестирования такого кода будет замокать (опасно! замокать в данном случае является глаголом!) HTTPClient, которым пользуется MyApp.TwitterClien:


          mock(HTTPClient, :get, to_return: %{..., "username" => "josevalim", ...})

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


          Не так быстро. Основная проблема при моке HTTPClient заключается в создании сильной внешней зависимости [англ. coupling везде переведена как "зависимость" — прим. перев.] к конкретному HTTPClient. Например, если вы решите использовать новый более быстрый HTTP-клиент, не изменяя поведение приложения, большая часть ваших интеграционных тестов упадет, потому что все они зависят от конкретного замоканного HTTPClient. Другими словами, изменение реализации без изменения поведения системы все равно приводит к падению тестов. Это плохой знак.


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


          Решение


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


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


          defmodule MyApp.MyController do
              @twitter_api Application.get_env(:my_app, :twitter_api)
          
              def show(conn, %{"username" => username}) do
                  # ...
                  @twitter_api.get_username(username)
                  # ...
              end
          end

          Соответствующие настройки для различных окружений:


          # config/dev.exs
          config :my_app, :twitter_api, MyApp.Twitter.Sandbox
          
          # config/test.exs
          config :my_app, :twitter_api, MyApp.Twitter.InMemory
          
          # config/prod.exs
          config :my_app, :twitter_api, MyApp.Twitter.HTTPClient

          Сейчас мы можем выбрать лучшую стратегию получения данных из Twitter для каждого из окружений. Sandbox может быть полезен, если Twitter предоставляет какой-нибудь sandbox для разработки. Наша замоканная версия HTTPClient позволяла избежать реальных HTTP-запросов. Реализация этой же функциональности в данном случае:


          defmodule MyApp.Twitter.InMemory do
              def get_username("josevalim") do
                  %MyApp.Twitter.User{
                      username: "josevalim"
                  }
              end
          end

          Код получился простым и чистым, а сильной внешней зависимости от HTTPClient больше нет. MyApp.Twitter.InMemory является моком, то есть существительным, и для его создания вам не нужны никакие библиотеки!


          Необходимость явных контрактов


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


          Мы уже имеем три реализации Twitter API и лучше сделать их контракты явными. В Elixir описать явный контракт можно с помощью behaviour:


          defmodule MyApp.Twitter do
              @doc "..."
              @callback get_username(username :: String.t) :: %MyApp.Twitter.User{}
          
              @doc "..."
              @callback followers_for(username :: String.t) :: [%MyApp.Twitter.User{}]
          end

          Теперь добавьте @behaviour MyApp.Twitter в каждый модуль, который реализует этот контракт, и Elixir поможет вам создать ожидаемый API.


          В Elixir мы полагаемся на такие behaviours постоянно: когда используем Plug, когда работаем с базой данных в Ecto, когда тестируем Phoenix channels и так далее.


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


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


          [MyApp] -> [HTTPClient] -> [Twitter API]


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


          [MyApp] -> [MyApp.Twitter (contract)]


          [MyApp.Twitter.HTTP (contract impl)] -> [HTTPClient] -> [Twitter API]


          Тесты такого приложения изолированы от HTTPClient и от Twitter API. Но как нам протестировать MyApp.Twitter.HTTP?


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


          Лично я бы протестировал MyApp.Twitter.HTTP на реальном Twitter API, запуская эти тесты по-необходимости во время разработки и каждый раз при сборке проекта. Система тегов в ExUnit — библиотеке для тестирования в Elixir — реализует такое поведение:


          defmodule MyApp.Twitter.HTTPTest do
              use ExUnit.Case, async: true
          
              # Эти тесты будут работать с Twitter API
              @moduletag :twitter_api
          
              # ...
          end

          Исключим тесты с Twitter API:


          ExUnit.configure exclude: [:twitter_api]

          При необходимости включим их в общий тестовый прогон:


          mix test --include twitter_api


          Также можно запустить их отдельно:


          mix test --only twitter_api


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


          1. Изменение в HTTPClient приводит только к падению тестов на MyApp.Twitter.HTTP
          2. Вы не мокаете (осторожно! мок в данном случае является глаголом!) HTTPClient. Вместо этого, вы передаете его как зависимость через файл конфигурации, подобно тому, как мы делали для Twitter API
          3. Вам все еще нужен способ протестировать работу вашего клиента до выкатки в production.

          Вместо создания мока HTTPClient можно поднять dummy-сервер, который будет эмулировать Twitter API. bypass — один из проектов, который может в этом помочь. Все возможные варианты вы должны обсудить со своей командой.


          Примечания


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


          Создание "тестируемого" кода


          Цитата из elixir-talk mailing list:


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

          Я бы сказал, что речь идет не о создании "тестируемого" кода, а об улучшении дизайна [от англ. design of your code — прим. перев.].


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


          Нет ничего плохого в нежелании уменьшать зависимость между компонентами. Например, если речь идет о модуле работы с URI [имеется ввиду модуль URI для Elixir — прим. перев.]. Но если мы говорим о чем-то таком же сложном, как внешнее API, определение явного контракта и наличие возможности заменять реализацию этого контракта сделает ваш код удобным и простым в сопровождении.


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


          Локальные моки


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


          defmodule MyModule do
              def my_function do
                  # ...
                  SomeDependency.heavy_work(arg1, arg2)
                  # ...
              end
          end

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


          defmodule MyModule do
              def my_function(heavy_work \\ &SomeDependency.heavy_work/2) do
                  # ...
                  heavy_work.(arg1, arg2)
                  # ...
              end
          end

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


          test "my function performs heavy work" do
              # Симулируем долгое вычисление с помощью отправки сообщения тесту
              heavy_work = fn(_, _) -> 
                  send(self(), :heavy_work)
              end
          
              MyModule.my_function(heavy_work)
          
              assert_received :heavy_work
          end

          Или, как было описано ранее, можно определить контракт и передать модуль целиком:


          defmodule MyModule do
              def my_function(dependency \\ SomeDependency)
                  # ...
                  dependency.heavy_work(arg1, arg2)
                  # ...
              end
          end

          Изменим тест:


          test "my function performs heavy work" do
            # Симулируем долгое вычисление с помощью отправки сообщения тесту
            defmodule TestDependency do
              def heavy_work(_arg1, _arg2) do
                send self(), :heavy_work
              end
            end
          
            MyModule.my_function(TestDependency)
          
            assert_received :heavy_work
          end

          Вы также можете представить зависимость в виде data structure и определить контракт с помощью protocol.


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


          Мок — это существительное


          Лучше думать о моках как о существительных. Вместо того, чтобы мокать API (мокать — глагол), нужно создать мок (мок — существительное), который реализует необходимый API.


          Большинство проблем от использования моков возникают, когда они используются как глаголы. Если вы мокаете что-то, вы изменяете уже существующие объекты, и зачастую эти изменения являются глобальными. Например, когда мы мокаем модуль SomeDependency, он изменится глобально:


          mock(SomeDependency, :heavy_work, to_return: true)

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


          Библиотеки для создания моков


          После прочитанного у вас может возникнуть вопрос: "Нужно ли отказываться от библиотек для создания моков?"


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


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


          Заключение


          Одна из задач тестирования системы — нахождение правильных контрактов и границ между компонентами. Использование моков только в случае наличия явного контракта позволит вам:


          1. Защититься от засилья моков, так как контракты будут создаваться только для необходимых частей системы. Как было упомянуто выше, вряд ли вы захотите прятать взаимодействие со стандартными модулями URI и Enum за контрактом.
          2. Упростить поддержку компонентов. При добавлении новой функциональности к зависимости, вам нужно обновить контракт (добавить новый @callback в Elixir). Бесконечный рост @callback укажет на зависимость, которая берет на себя слишком много ответственности, и вы сможете раньше расправиться с проблемой.
          3. Сделать вашу систему пригодной для тестирования, потому что взаимодействие между сложными компонентами будет изолировано.

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

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

          https://habrahabr.ru/post/338066/


          Метки:  

          [Перевод] Моки и явные контракты

          Суббота, 16 Сентября 2017 г. 19:08 + в цитатник
          Telichkin сегодня в 19:08 Разработка

          Моки и явные контракты

          • Перевод

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


          Ниже представлен вольный перевод статьи, в которой Jos'e Valim — создатель языка Elixir — высказал своё мнение на проблему использования моков, с которым я полностью согласен.




          Несколько дней назад я поделился своими мыслями по поводу моков в Twitter:



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


          Что такое мок?


          Воспользуемся определением из англоязычной википедии: мок — настраиваемый объект, который имитирует поведение реального объекта. Я сделаю акцент на этом позже, но для меня мок — это всегда существительное, а не глагол [для наглядности, глагол mock везде будет переводиться как "замокать" — прим. перев.].


          На примере внешнего API


          Давайте рассмотрим стандартный пример из реальной жизни: внешнее API.


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


          defmodule MyApp.MyController do
              def show(conn, %{"username" => username}) do
                  # ...
                  MyApp.TwitterClient.get_username(username)
                  # ...
              end
          end

          Стандартным подходом при тестирования такого кода будет замокать (опасно! замокать в данном случае является глаголом!) HTTPClient, которым пользуется MyApp.TwitterClien:


          mock(HTTPClient, :get, to_return: %{..., "username" => "josevalim", ...})

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


          Не так быстро. Основная проблема при моке HTTPClient заключается в создании сильной внешней зависимости [англ. coupling везде переведена как "зависимость" — прим. перев.] к конкретному HTTPClient. Например, если вы решите использовать новый более быстрый HTTP-клиент, не изменяя поведение приложения, большая часть ваших интеграционных тестов упадет, потому что все они зависят от конкретного замоканного HTTPClient. Другими словами, изменение реализации без изменения поведения системы все равно приводит к падению тестов. Это плохой знак.


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


          Решение


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


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


          defmodule MyApp.MyController do
              @twitter_api Application.get_env(:my_app, :twitter_api)
          
              def show(conn, %{"username" => username}) do
                  # ...
                  @twitter_api.get_username(username)
                  # ...
              end
          end

          Соответствующие настройки для различных окружений:


          # config/dev.exs
          config :my_app, :twitter_api, MyApp.Twitter.Sandbox
          
          # config/test.exs
          config :my_app, :twitter_api, MyApp.Twitter.InMemory
          
          # config/prod.exs
          config :my_app, :twitter_api, MyApp.Twitter.HTTPClient

          Сейчас мы можем выбрать лучшую стратегию получения данных из Twitter для каждого из окружений. Sandbox может быть полезен, если Twitter предоставляет какой-нибудь sandbox для разработки. Наша замоканная версия HTTPClient позволяла избежать реальных HTTP-запросов. Реализация этой же функциональности в данном случае:


          defmodule MyApp.Twitter.InMemory do
              def get_username("josevalim") do
                  %MyApp.Twitter.User{
                      username: "josevalim"
                  }
              end
          end

          Код получился простым и чистым, а сильной внешней зависимости от HTTPClient больше нет. MyApp.Twitter.InMemory является моком, то есть существительным, и для его создания вам не нужны никакие библиотеки!


          Необходимость явных контрактов


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


          Мы уже имеем три реализации Twitter API и лучше сделать их контракты явными. В Elixir описать явный контракт можно с помощью behaviour:


          defmodule MyApp.Twitter do
              @doc "..."
              @callback get_username(username :: String.t) :: %MyApp.Twitter.User{}
          
              @doc "..."
              @callback followers_for(username :: String.t) :: [%MyApp.Twitter.User{}]
          end

          Теперь добавьте @behaviour MyApp.Twitter в каждый модуль, который реализует этот контракт, и Elixir поможет вам создать ожидаемый API.


          В Elixir мы полагаемся на такие behaviours постоянно: когда используем Plug, когда работаем с базой данных в Ecto, когда тестируем Phoenix channels и так далее.


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


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


          [MyApp] -> [HTTPClient] -> [Twitter API]


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


          [MyApp] -> [MyApp.Twitter (contract)]


          [MyApp.Twitter.HTTP (contract impl)] -> [HTTPClient] -> [Twitter API]


          Тесты такого приложения изолированы от HTTPClient и от Twitter API. Но как нам протестировать MyApp.Twitter.HTTP?


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


          Лично я бы протестировал MyApp.Twitter.HTTP на реальном Twitter API, запуская эти тесты по-необходимости во время разработки и каждый раз при сборке проекта. Система тегов в ExUnit — библиотеке для тестирования в Elixir — реализует такое поведение:


          defmodule MyApp.Twitter.HTTPTest do
              use ExUnit.Case, async: true
          
              # Эти тесты будут работать с Twitter API
              @moduletag :twitter_api
          
              # ...
          end

          Исключим тесты с Twitter API:


          ExUnit.configure exclude: [:twitter_api]

          При необходимости включим их в общий тестовый прогон:


          mix test --include twitter_api


          Также можно запустить их отдельно:


          mix test --only twitter_api


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


          1. Изменение в HTTPClient приводит только к падению тестов на MyApp.Twitter.HTTP
          2. Вы не мокаете (осторожно! мок в данном случае является глаголом!) HTTPClient. Вместо этого, вы передаете его как зависимость через файл конфигурации, подобно тому, как мы делали для Twitter API
          3. Вам все еще нужен способ протестировать работу вашего клиента до выкатки в production.

          Вместо создания мока HTTPClient можно поднять dummy-сервер, который будет эмулировать Twitter API. bypass — один из проектов, который может в этом помочь. Все возможные варианты вы должны обсудить со своей командой.


          Примечания


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


          Создание "тестируемого" кода


          Цитата из elixir-talk mailing list:


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

          Я бы сказал, что речь идет не о создании "тестируемого" кода, а об улучшении дизайна [от англ. design of your code — прим. перев.].


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


          Нет ничего плохого в нежелании уменьшать зависимость между компонентами. Например, если речь идет о модуле работы с URI [имеется ввиду модуль URI для Elixir — прим. перев.]. Но если мы говорим о чем-то таком же сложном, как внешнее API, определение явного контракта и наличие возможности заменять реализацию этого контракта сделает ваш код удобным и простым в сопровождении.


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


          Локальные моки


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


          defmodule MyModule do
              def my_function do
                  # ...
                  SomeDependency.heavy_work(arg1, arg2)
                  # ...
              end
          end

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


          defmodule MyModule do
              def my_function(heavy_work \\ &SomeDependency.heavy_work/2) do
                  # ...
                  heavy_work.(arg1, arg2)
                  # ...
              end
          end

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


          test "my function performs heavy work" do
              # Симулируем долгое вычисление с помощью отправки сообщения тесту
              heavy_work = fn(_, _) -> 
                  send(self(), :heavy_work)
              end
          
              MyModule.my_function(heavy_work)
          
              assert_received :heavy_work
          end

          Или, как было описано ранее, можно определить контракт и передать модуль целиком:


          defmodule MyModule do
              def my_function(dependency \\ SomeDependency)
                  # ...
                  dependency.heavy_work(arg1, arg2)
                  # ...
              end
          end

          Изменим тест:


          test "my function performs heavy work" do
            # Симулируем долгое вычисление с помощью отправки сообщения тесту
            defmodule TestDependency do
              def heavy_work(_arg1, _arg2) do
                send self(), :heavy_work
              end
            end
          
            MyModule.my_function(TestDependency)
          
            assert_received :heavy_work
          end

          Вы также можете представить зависимость в виде data structure и определить контракт с помощью protocol.


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


          Мок — это существительное


          Лучше думать о моках как о существительных. Вместо того, чтобы мокать API (мокать — глагол), нужно создать мок (мок — существительное), который реализует необходимый API.


          Большинство проблем от использования моков возникают, когда они используются как глаголы. Если вы мокаете что-то, вы изменяете уже существующие объекты, и зачастую эти изменения являются глобальными. Например, когда мы мокаем модуль SomeDependency, он изменится глобально:


          mock(SomeDependency, :heavy_work, to_return: true)

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


          Библиотеки для создания моков


          После прочитанного у вас может возникнуть вопрос: "Нужно ли отказываться от библиотек для создания моков?"


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


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


          Заключение


          Одна из задач тестирования системы — нахождение правильных контрактов и границ между компонентами. Использование моков только в случае наличия явного контракта позволит вам:


          1. Защититься от засилья моков, так как контракты будут создаваться только для необходимых частей системы. Как было упомянуто выше, вряд ли вы захотите прятать взаимодействие со стандартными модулями URI и Enum за контрактом.
          2. Упростить поддержку компонентов. При добавлении новой функциональности к зависимости, вам нужно обновить контракт (добавить новый @callback в Elixir). Бесконечный рост @callback укажет на зависимость, которая берет на себя слишком много ответственности, и вы сможете раньше расправиться с проблемой.
          3. Сделать вашу систему пригодной для тестирования, потому что взаимодействие между сложными компонентами будет изолировано.

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

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

          https://habrahabr.ru/post/338066/


          Метки:  

          [Из песочницы] Классический 2д квест или как прошли наши два года разработки. Часть 1

          Суббота, 16 Сентября 2017 г. 18:58 + в цитатник
          MaikShamrock сегодня в 18:58 Разработка

          Классический 2д квест или как прошли наши два года разработки. Часть 1

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

          Внимание! Под катом есть картинки и видосики, осторожно, траффик!

          image

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

          Поехали!

          Предыстория


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

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

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

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

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

          Видос с тех времен сохранился в недрах ютуба. Давайте посмотрим:





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

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

          Зима — Весна 2014


          Огромным плюсом стало то, что Саня наваял целый 60 страничный документ с полными текстами сцен и даже построил дерево того, как будет развиваться действие в игре. Это, честно говоря, меня немало удивило, потому как дизайн документов я отродясь не видел, и, честно говоря, считал это чутка бредом, еще со времен примеров 1С и их «The Ryaba Revenge», но наличие этого материала сильно облегчило построение игры в дальнейшем.

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

          Итак, план был прост — надо:

          а) делать арт, поскольку его надо много (в игре должно было быть 300+ сцен)
          б) брать и пробовать делать игру на каком-то движке

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

          Движок. Учитывая те особенности игры, которые должны были в ней быть, то требования были простыми — это новелла со своими примочками. Всякие новельные движки я отбросил сразу, из-за возможных проблем со встраиванием дроидовских, ios-овских примочек на этапе запуска, был angengine, но он был только под дроида. Пробовал libgdx, который на тот момент был еще сыроват, cocos2d пугал С++, которым я владел, но после использования С# в университете, к которому возвращаться уже не хотелось. И, наконец, был опробован CoronaSDK, который имел достаточно удобную справку с примерами и плагины, которые обещали быть кроссплатформенными. Движок на тот момент был платным (около 100$), но ради удобства можно было немного потратиться. Язык программирования, используемый Corona, мне тоже в принципе понравился своей простотой — это всем известный скриптовый язык Lua.

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

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

          image

          image

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

          image

          Начало было положено и мы перешли уже к построению сцен самой игры:

          image
          Скетч сцены

          image
          Сцена в монохроме

          image
          Готовая сцена игры

          image
          Первая версия главного экрана игры, позднее она очень сильно изменится

          Дальше случилось то, чего мы не ждали, а именно — человеческий фактор. Мы работали с двумя художниками: вначале они рисовали сцены, взаимодействовали с нами, и всё шло вроде бы хорошо, но в один прекрасный момент художник просто исчезает и больше не выходит с нами на связь. Прождав некоторое время, мы начали поиски другого художника для продолжения работ, ситуация повторилась вновь. Белиссимо!

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

          Тем временем у нас всё-таки добавилось немного арта, из которого я уже собрал демку в CoronaSDK:

          image
          Запуск во встроенном эмуляторе

          image
          Запуск игры на телефоне

          Впечатления о движке? Ну в целом — интересная штука, но теперь, уже смотря на этот движок в ретроспективе, я понимаю, что сделать на таком можно только достаточно простой проектик: ни нормального управления классами, раскиданные данные по куче отдельных .lua файлов, обертки для интерфейса и.т.д. В целом это рабочая система, на которой, если извернуться, то можно сделать проект, но это не сравнится с тем, как если писать на нормальном хорошем ООП-языке типа C# или Java. Таким образом, наваяв еще пару демо, запустив пару раз на телефоне, я всё-таки понял, что целиком писать проект на CoronaSDK я не хочу.

          Помимо этого были испытаны Unity3D (на тот момент в нем нормально не поддерживалась работа с 2д, многое потребовалось бы пилить самому и пытаться завернуть это в 3д пространство, пару дней провозюкавшись с камерой и выполнив пару туториалов я забросил это дело), был также испытан движок Construct для игр на новомодном HTML5, но данный движок был очень сильно заточен под свои компоненты и также откинут в сторону.

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

          Лето-осень 2014


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

          Ну правда не совсем судьбой… У Женьки уже был достаточный опыт работы не только с играми, но и с другими проектами игровой тематики, в том числе и карточными играми, где он также выступал в качестве художника, победы и отметки за участие в конкурсах, ну и.т.д. В общем, человеком он был и есть в плане графики продвинутым!

          image
          Наброски интерфейса

          image
          Один из новых артов

          И один из обучающих роликов созданных самим же автором:





          image
          Скетчи

          В общем, Женька смог! В таком темпе мы и двигались дальше: Саня напару с Женей обсуждали что и как рисовать и ставить в игре, я писал код и пробовал движок LibGDX, потихоньку шло время, и к концу 2014 года у нас было достаточно графики и умения, чтобы выпустить своё первое маленькое приложение в GooglePlay, а именно — демо-версию нашей будущей игры.

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

          — мы хотели получить опыт публикации приложения в маркете
          — мы хотели посмотреть, как игроки воспримут игру такого жанра (всё-таки текстовые квесты и новеллы — это довольно специфическая ниша)
          — мы хотели посмотреть на доход от рекламы в бесплатном приложении, и как игроки будут реагировать на неё

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

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

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

          Продолжение следует…
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338074/


          Метки:  

          [Из песочницы] Классический 2д квест или как прошли наши два года разработки. Часть 1

          Суббота, 16 Сентября 2017 г. 18:58 + в цитатник
          MaikShamrock сегодня в 18:58 Разработка

          Классический 2д квест или как прошли наши два года разработки. Часть 1

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

          Внимание! Под катом есть картинки и видосики, осторожно, траффик!

          image

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

          Поехали!

          Предыстория


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

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

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

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

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

          Видос с тех времен сохранился в недрах ютуба. Давайте посмотрим:





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

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

          Зима — Весна 2014


          Огромным плюсом стало то, что Саня наваял целый 60 страничный документ с полными текстами сцен и даже построил дерево того, как будет развиваться действие в игре. Это, честно говоря, меня немало удивило, потому как дизайн документов я отродясь не видел, и, честно говоря, считал это чутка бредом, еще со времен примеров 1С и их «The Ryaba Revenge», но наличие этого материала сильно облегчило построение игры в дальнейшем.

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

          Итак, план был прост — надо:

          а) делать арт, поскольку его надо много (в игре должно было быть 300+ сцен)
          б) брать и пробовать делать игру на каком-то движке

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

          Движок. Учитывая те особенности игры, которые должны были в ней быть, то требования были простыми — это новелла со своими примочками. Всякие новельные движки я отбросил сразу, из-за возможных проблем со встраиванием дроидовских, ios-овских примочек на этапе запуска, был angengine, но он был только под дроида. Пробовал libgdx, который на тот момент был еще сыроват, cocos2d пугал С++, которым я владел, но после использования С# в университете, к которому возвращаться уже не хотелось. И, наконец, был опробован CoronaSDK, который имел достаточно удобную справку с примерами и плагины, которые обещали быть кроссплатформенными. Движок на тот момент был платным (около 100$), но ради удобства можно было немного потратиться. Язык программирования, используемый Corona, мне тоже в принципе понравился своей простотой — это всем известный скриптовый язык Lua.

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

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

          image

          image

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

          image

          Начало было положено и мы перешли уже к построению сцен самой игры:

          image
          Скетч сцены

          image
          Сцена в монохроме

          image
          Готовая сцена игры

          image
          Первая версия главного экрана игры, позднее она очень сильно изменится

          Дальше случилось то, чего мы не ждали, а именно — человеческий фактор. Мы работали с двумя художниками: вначале они рисовали сцены, взаимодействовали с нами, и всё шло вроде бы хорошо, но в один прекрасный момент художник просто исчезает и больше не выходит с нами на связь. Прождав некоторое время, мы начали поиски другого художника для продолжения работ, ситуация повторилась вновь. Белиссимо!

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

          Тем временем у нас всё-таки добавилось немного арта, из которого я уже собрал демку в CoronaSDK:

          image
          Запуск во встроенном эмуляторе

          image
          Запуск игры на телефоне

          Впечатления о движке? Ну в целом — интересная штука, но теперь, уже смотря на этот движок в ретроспективе, я понимаю, что сделать на таком можно только достаточно простой проектик: ни нормального управления классами, раскиданные данные по куче отдельных .lua файлов, обертки для интерфейса и.т.д. В целом это рабочая система, на которой, если извернуться, то можно сделать проект, но это не сравнится с тем, как если писать на нормальном хорошем ООП-языке типа C# или Java. Таким образом, наваяв еще пару демо, запустив пару раз на телефоне, я всё-таки понял, что целиком писать проект на CoronaSDK я не хочу.

          Помимо этого были испытаны Unity3D (на тот момент в нем нормально не поддерживалась работа с 2д, многое потребовалось бы пилить самому и пытаться завернуть это в 3д пространство, пару дней провозюкавшись с камерой и выполнив пару туториалов я забросил это дело), был также испытан движок Construct для игр на новомодном HTML5, но данный движок был очень сильно заточен под свои компоненты и также откинут в сторону.

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

          Лето-осень 2014


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

          Ну правда не совсем судьбой… У Женьки уже был достаточный опыт работы не только с играми, но и с другими проектами игровой тематики, в том числе и карточными играми, где он также выступал в качестве художника, победы и отметки за участие в конкурсах, ну и.т.д. В общем, человеком он был и есть в плане графики продвинутым!

          image
          Наброски интерфейса

          image
          Один из новых артов

          И один из обучающих роликов созданных самим же автором:





          image
          Скетчи

          В общем, Женька смог! В таком темпе мы и двигались дальше: Саня напару с Женей обсуждали что и как рисовать и ставить в игре, я писал код и пробовал движок LibGDX, потихоньку шло время, и к концу 2014 года у нас было достаточно графики и умения, чтобы выпустить своё первое маленькое приложение в GooglePlay, а именно — демо-версию нашей будущей игры.

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

          — мы хотели получить опыт публикации приложения в маркете
          — мы хотели посмотреть, как игроки воспримут игру такого жанра (всё-таки текстовые квесты и новеллы — это довольно специфическая ниша)
          — мы хотели посмотреть на доход от рекламы в бесплатном приложении, и как игроки будут реагировать на неё

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

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

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

          Продолжение следует…
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338074/


          Метки:  

          [Из песочницы] ASO в iOS 11: сводная таблица изменений

          Суббота, 16 Сентября 2017 г. 18:23 + в цитатник
          belltane сегодня в 18:23 Маркетинг

          ASO в iOS 11: сводная таблица изменений

          Цель поста — собрать в одном месте актуальную рабочую таблицу, которая поможет участникам сообщества, так или иначе связанным с мобильной поисковой оптимизацией (ASO), оперативно отреагировать на изменения магазина Apple, готовящиеся к релизу 19 сентября.

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



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



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

          И самое интересное:



          Изменения механики магазина приложений:



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

          Если Вы видите в таблице несоответствие истине, буду рад комментам. Также приветствуются дополнения, на полноту данных этот анализ не претендует.

          Всем неограниченной органики! ;)
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338070/


          Метки:  

          [Из песочницы] ASO в iOS 11: сводная таблица изменений

          Суббота, 16 Сентября 2017 г. 18:23 + в цитатник
          belltane сегодня в 18:23 Маркетинг

          ASO в iOS 11: сводная таблица изменений

          Цель поста — собрать в одном месте актуальную рабочую таблицу, которая поможет участникам сообщества, так или иначе связанным с мобильной поисковой оптимизацией (ASO), оперативно отреагировать на изменения магазина Apple, готовящиеся к релизу 19 сентября.

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



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



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

          И самое интересное:



          Изменения механики магазина приложений:



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

          Если Вы видите в таблице несоответствие истине, буду рад комментам. Также приветствуются дополнения, на полноту данных этот анализ не претендует.

          Всем неограниченной органики! ;)
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338070/


          Метки:  

          Приглашаем на Android Devs Meetup 22 сентября

          Суббота, 16 Сентября 2017 г. 16:36 + в цитатник

          Метки:  

          Приглашаем на Android Devs Meetup 22 сентября

          Суббота, 16 Сентября 2017 г. 16:36 + в цитатник

          Метки:  

          Стриминговый сервис Roku собрался на IPO: что может пойти не так

          Суббота, 16 Сентября 2017 г. 13:26 + в цитатник
          itinvest сегодня в 13:26 Разное

          Стриминговый сервис Roku собрался на IPO: что может пойти не так



            Изображение: Mike Mozart, CC BY 2.0

            Сервис потокового телевидения Roku Inc. подал документы для первичного размещения акций на бирже. Крупнейший игрок американского рынка ожидает оценки в $1 млрд. Однако, компания заявила и о ряде рисков, которые, по мнению инвесторов, могут помешать успешному IPO.

            Что такое Roku


            Roku наиболее известна своими приставками, которые позволяют транслировать Netflix и другое потоковое видео. Компания возникла как подразделение Netflix Inc., который инвестировал в неё $5,7 млн. В 2009 году Netflix продал свою долю за $1,7 млн. Но Roku до сих пор работает с этой мультимедийной компанией и даже арендует офисное помещение у Netflix.

            Свою первую ТВ-приставку Roku выпустила в 2007 году. На тот момент стриминговых устройств с аналогичным функционалом практически не было (за исключением Apple TV), и Roku быстро завоевала рынок. Сегодня она – лидер в США в гаджетах интернет-телевидения.

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

            Не обошлось у Roku и без скандалов. Компания столкнулась с проблемами при продаже своих устройств в Мексике – хакеры начали транслировать пиратский контент от HBO, ESPN и других через приставки Roku. В результате мексиканский суд заблокировал продажи устройства по всей стране стране, что стоило компании почти $2 млн.

            Выход на IPO


            Слухи об IPO Roku ходят уже давно – компания задумывалась о нём ещё в 2014 году, но предпочла оставаться частной. Однако в 2017 году Roku всё же пересмотрела своё решение и наняла андеррайтеров. Ими стали Morgan Stanley, Citigroup и Allen & Co. Одной из причин публичного размещения акций Roku в этом году может стать желание успокоить людей, которые помогли финансировать компанию на раннем этапе.

            Возможно также, что менеджмент компании был вдохновлен примерами двух других технологичных IPO – Snap Inc. и Blue Apron Holdings Inc. Размещение Snap Inc. стало рекордным по объемам, акции компании в день IPO торговались по $17 за штуку, но уже 1 сентября упали в цене до $14,27. Тем временем стоимость Blue Apron снизилась на 52% с момента его IPO.

            Что произойдёт с Roku, пока непонятно. За первые шесть месяцев 2017 года продажи приставок компании снизились на 2% до $117,3 млн по сравнению с аналогичным периодом прошлого года. Хотя компания фактически продает на 37% больше оборудования, в заявлении об IPO говорится, что она снижает средние отпускные цены. С другой стороны, доходы от платформы выросли на 91% до $43,1 млн за тот же шестимесячный период.

            Что касается деталей IPO, Roku решила предложить два класса акций для инвесторов. Обыкновенные акции класса A, принадлежащие руководителям, директорам и нескольким ранним инвесторам, будут равняться одному голосу за акцию. Акции класса В будут равняться 10 голосам за акцию и конвертируется в одну акцию обыкновенных акций класса А.

            Этот шаг позволяет инсайдерам сохранять более сильный контроль над компанией после IPO. Сейчас 28,4% акций компании принадлежат CEO компании Энтони Вуду, 35% – венчурной компании Menlo Ventures, 12,9% – фонду Fidelity Investments Inc. и 7% – 21st Century Fox Inc.

            Что может пойти не так


            Roku является лидером в области потокового телевидения, на долю которого приходится 27% американского рынка. И тем не менее, эксперты не видят чересчур радужных перспектив для IPO компании.

            Основный бизнес Roku сейчас – это продажа устройств. Недавние размещения компаний, которые специализируются на продаже продуктов, оказались не слишком успешными. Например, цена акций GoPro упала на 38% в прошлом году и торгуется на 63% ниже цены IPO в 2014 году. Другой игрок – Fitbit – добился еще худших результатов: акции снизились на 61% за последние 12 месяцев и на 71% в сравнении с первоначальной ценой в 2015 году.

            Та же судьба, вероятно, ожидает и Roku – продукт насыщает рынок, после чего рост доходов компании постепенно угасает. Особенно если учесть, что у Roku есть сильные конкуренты (приставки от Apple и Google) с более крупными бюджетами и более узнаваемыми брендами. Сейчас компании приходится буквально бороться за полки в розничных магазинах. Усугубляет ситуацию и то, что один из крупнейших онлайн-продавцов Roku – Amazon.com – также запустил конкурирующий продукт и, вероятно, вовсе откажется предлагать на своем сайте устройства Roku.

            Однако, в отличие от Fitbit и GoPro у Roku есть шанс заверить инвесторов в своей перспективности. В июле 46% своей выручки компания получила от подписки и рекламы на платформе. Что еще более важно, продажи в этом сегменте выросли на 95%, превратив Roku в производителя ПО и контентную компанию.

            Когда зрители смотрят CBS News и Vice, Roku получает доходы от рекламы. Также компания получает доход от подписных сервисов, таких как HBO Now и Hulu. Некоторые производители телевизоров платят за внедрение сервиса Roku в свои устройства. На предстоящем roadshow Roku, вероятно, попытается убедить инвесторов в том, что будущее компании как раз в этих направлениях бизнеса.

            Другие материалы по теме финансов и фондового рынка от ITinvest:


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

            https://habrahabr.ru/post/338056/


            Метки:  

            Стриминговый сервис Roku собрался на IPO: что может пойти не так

            Суббота, 16 Сентября 2017 г. 13:26 + в цитатник
            itinvest сегодня в 13:26 Разное

            Стриминговый сервис Roku собрался на IPO: что может пойти не так



              Изображение: Mike Mozart, CC BY 2.0

              Сервис потокового телевидения Roku Inc. подал документы для первичного размещения акций на бирже. Крупнейший игрок американского рынка ожидает оценки в $1 млрд. Однако, компания заявила и о ряде рисков, которые, по мнению инвесторов, могут помешать успешному IPO.

              Что такое Roku


              Roku наиболее известна своими приставками, которые позволяют транслировать Netflix и другое потоковое видео. Компания возникла как подразделение Netflix Inc., который инвестировал в неё $5,7 млн. В 2009 году Netflix продал свою долю за $1,7 млн. Но Roku до сих пор работает с этой мультимедийной компанией и даже арендует офисное помещение у Netflix.

              Свою первую ТВ-приставку Roku выпустила в 2007 году. На тот момент стриминговых устройств с аналогичным функционалом практически не было (за исключением Apple TV), и Roku быстро завоевала рынок. Сегодня она – лидер в США в гаджетах интернет-телевидения.

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

              Не обошлось у Roku и без скандалов. Компания столкнулась с проблемами при продаже своих устройств в Мексике – хакеры начали транслировать пиратский контент от HBO, ESPN и других через приставки Roku. В результате мексиканский суд заблокировал продажи устройства по всей стране стране, что стоило компании почти $2 млн.

              Выход на IPO


              Слухи об IPO Roku ходят уже давно – компания задумывалась о нём ещё в 2014 году, но предпочла оставаться частной. Однако в 2017 году Roku всё же пересмотрела своё решение и наняла андеррайтеров. Ими стали Morgan Stanley, Citigroup и Allen & Co. Одной из причин публичного размещения акций Roku в этом году может стать желание успокоить людей, которые помогли финансировать компанию на раннем этапе.

              Возможно также, что менеджмент компании был вдохновлен примерами двух других технологичных IPO – Snap Inc. и Blue Apron Holdings Inc. Размещение Snap Inc. стало рекордным по объемам, акции компании в день IPO торговались по $17 за штуку, но уже 1 сентября упали в цене до $14,27. Тем временем стоимость Blue Apron снизилась на 52% с момента его IPO.

              Что произойдёт с Roku, пока непонятно. За первые шесть месяцев 2017 года продажи приставок компании снизились на 2% до $117,3 млн по сравнению с аналогичным периодом прошлого года. Хотя компания фактически продает на 37% больше оборудования, в заявлении об IPO говорится, что она снижает средние отпускные цены. С другой стороны, доходы от платформы выросли на 91% до $43,1 млн за тот же шестимесячный период.

              Что касается деталей IPO, Roku решила предложить два класса акций для инвесторов. Обыкновенные акции класса A, принадлежащие руководителям, директорам и нескольким ранним инвесторам, будут равняться одному голосу за акцию. Акции класса В будут равняться 10 голосам за акцию и конвертируется в одну акцию обыкновенных акций класса А.

              Этот шаг позволяет инсайдерам сохранять более сильный контроль над компанией после IPO. Сейчас 28,4% акций компании принадлежат CEO компании Энтони Вуду, 35% – венчурной компании Menlo Ventures, 12,9% – фонду Fidelity Investments Inc. и 7% – 21st Century Fox Inc.

              Что может пойти не так


              Roku является лидером в области потокового телевидения, на долю которого приходится 27% американского рынка. И тем не менее, эксперты не видят чересчур радужных перспектив для IPO компании.

              Основный бизнес Roku сейчас – это продажа устройств. Недавние размещения компаний, которые специализируются на продаже продуктов, оказались не слишком успешными. Например, цена акций GoPro упала на 38% в прошлом году и торгуется на 63% ниже цены IPO в 2014 году. Другой игрок – Fitbit – добился еще худших результатов: акции снизились на 61% за последние 12 месяцев и на 71% в сравнении с первоначальной ценой в 2015 году.

              Та же судьба, вероятно, ожидает и Roku – продукт насыщает рынок, после чего рост доходов компании постепенно угасает. Особенно если учесть, что у Roku есть сильные конкуренты (приставки от Apple и Google) с более крупными бюджетами и более узнаваемыми брендами. Сейчас компании приходится буквально бороться за полки в розничных магазинах. Усугубляет ситуацию и то, что один из крупнейших онлайн-продавцов Roku – Amazon.com – также запустил конкурирующий продукт и, вероятно, вовсе откажется предлагать на своем сайте устройства Roku.

              Однако, в отличие от Fitbit и GoPro у Roku есть шанс заверить инвесторов в своей перспективности. В июле 46% своей выручки компания получила от подписки и рекламы на платформе. Что еще более важно, продажи в этом сегменте выросли на 95%, превратив Roku в производителя ПО и контентную компанию.

              Когда зрители смотрят CBS News и Vice, Roku получает доходы от рекламы. Также компания получает доход от подписных сервисов, таких как HBO Now и Hulu. Некоторые производители телевизоров платят за внедрение сервиса Roku в свои устройства. На предстоящем roadshow Roku, вероятно, попытается убедить инвесторов в том, что будущее компании как раз в этих направлениях бизнеса.

              Другие материалы по теме финансов и фондового рынка от ITinvest:


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

              https://habrahabr.ru/post/338056/


              Метки:  

              [Перевод] Твой софт никому не нужен. Или почему разработка ПО требует свежего подхода

              Суббота, 16 Сентября 2017 г. 11:19 + в цитатник

              Метки:  

              Обновления Magento, Защита от вредных администраторов, утечки данных, исполнения кода

              Пятница, 15 Сентября 2017 г. 22:07 + в цитатник
              kirmorozov вчера в 22:07 Разработка

              Обновления Magento, Защита от вредных администраторов, утечки данных, исполнения кода

                image

                Обновления Magento 2.1.9, 2.0.16, принесли множество заплаток в том числе от XSS, CSRF, неавторизированные утечки данных, защита от администраторов/операторов магазинов.
                Зацепило даже Magento 1.x, 1.9.3.6 и 1.14.3.6 получили обновления.

                Для простоты назовем плохого администратора/оператора — Одмин.
                Обновление: Добавлено описание того как происходит утечка данных о заказанных товарах.

                Критичный (1)


                APPSEC-1800: Remote Code Execution vulnerability in CMS and layouts
                Дословно: Одмин может написать код, который будет исполнен на сервере.
                Это все благодаря layoutам, в которых можно было вызывать исполнение кода.
                Когда-то можно было написать в Magento 1.x такую конфигурацию, которая позволяла показать содержимое любого файла на странице товара.
                Скорее всего используется что-то очень похожее или какой-то из блоков позволяет задать ему «вредный параметр».

                Высокий риск (3)


                APPSEC-1887 Одмин мог прочитать то, что не стоит читать.
                APPSEC-1850 Одмин мог удалить что-то очень важное, остановить работу магазина
                APPSEC-1851 RCE Одмин мог исполнить вредоносный код, последствия мы можем себе представить.

                Средний риск (29)


                APPSEC-1567 Покупатель мог получить данные о заказах, после доступа к какому-то, но нужно постараться сделать cookie.
                APPSEC-1769 Одмин мог записать sitemap не туда
                APPSEC-1713 Можно получить важные данные по системным ссылкам
                APPSEC-1852 XSS и CSRF Одмин мог заложить XSS и CSRF
                APPSEC-1482 Возможность отправить «не туда»
                APPSEC-1502 XSS Одмин мог назвать тип товара
                APPSEC-1494 XSS Если кто-то между сервером и xml с новостями вставит свой xml.
                APPSEC-1793 CRE Для серверов с Nginx можно закинуть и выполнить файл
                APPSEC-1819 Перехват сессии клиента, которая не устарела
                APPSEC-1802 CSRF Можно было захватить управление учетными записями клиентов
                APPSEC-1493 XSS В названии страницы можно было вписать код
                APPSEC-1755 CSRF Можно было наделать дел после входа клиента в систему
                APPSEC-1853 XSS и CSRF Одмин мог в модуле рассылок добавить
                APPSEC-1729 XSS Одмин мог добавить код в названии статуса заказа
                APPSEC-1591 XSS Одмин мог заложить в картинках товаров проблему
                APPSEC-1896 XSS Одмин мог в заказе заложить вредный код
                APPSEC-1673 XSS Одмин через SVG favicon подкинуть проблему
                APPSEC-1773 DoS Одмин мог указать ID страницы при создании лишить возможности создавать новые
                APPSEC-1577 XSS Одмин мог добавить код при активации интеграций
                APPSEC-1510 Одмин с ограниченными правами мог подменить favicon
                APPSEC-1545 XSS Одмин мог списать код в поля клиента
                APPSEC-1535 Одмин мог редактировать значения через быстрое редактирование в таблицах.
                APPSEC-1588 Вредитель мог «слить» данные о всех предыдущих заказах во время оформления заказа
                APPSEC-1701 Возможное переиспользование сессий API
                APPSEC-1630 Можно было ознакомиться со статусом обновления системы
                APPSEC-1628 Получение абсолютного пути на сервере
                APPSEC-1599 Браузер пытается использовать авто-подстановку входе в админ-панель

                Низкий риск (2)


                APPSEC-1709 Можно было получить почту администратора
                APPSEC-1495 Редактирование поле заказа без права просмотра

                Интересные атаки


                Интересны тем, что их можно отработать по Magento 1.x.
                APPSEC-1793 nginх очень часто применяется в боевых системах.
                APPSEC-1588 Можно было достать данные о клиентахкупленных товарах у конкурента.
                Используя идентификаторы строк заказа, пытаемся добавить товар к себе в корзину по идентификатору строки из заказа ранее, товар будет добавлен.
                Это не дает времени, заказа и даты, но если начать «следить» за магазином с момента X, то можно собрать данные о том, что покупали клиенты начиная с момента X. Чтоб собрать такие данные, нужно регулярно опрашивать систему.
                В момент X нужно узнать идентификатор последнего заказанного товара.
                Также можно собрать базу самых продаваемых товаров магазина, если товары не удалялись.

                Вместо концовки
                18 из 35 уязвимостей — атака изнутри, когда администратор магазина вредит самостоятельно.

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

                Источник: https://magento.com/security/patches/magento-2016-and-219-security-update
                Патчи можно посмотреть на Гитхабе: 2.015-2.0.16 и 2.1.8-2.1.9

                Обновление: Добавлено описание того как происходит утечка данных о заказанных товарах.
                Original source: habrahabr.ru (comments, light).

                https://habrahabr.ru/post/338052/


                Метки:  

                Поиск сообщений в rss_rss_hh_full
                Страницы: 1824 ... 1534 1533 [1532] 1531 1530 ..
                .. 1 Календарь