...

суббота, 19 декабря 2015 г.

[Перевод] Пол Грэм: Слово «хакер»

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


image
(На картинке изображены Стив Джобс и Стив Возняк с их проектом «blue box». Фото сделано Маргрет Возняк. Предоставлено с разрешения Стива Возняка.)

Оригинал — http://ift.tt/15UMxLw
Перевод — Щёкотова Яна
(предыдущий перевод — «Месть Ботанов»)

Слово «Хакер»
Апрель, 2004

В СМИ слово «хакер» используют для описания того, кто взламывает компьютеры. В среде разработчиков программного обеспечения это слово означает искусного программиста. Но между этими двумя понятиями существует связь. Для программистов «хакер» ассоциируется с мастерством в самом буквальном смысле слова: некто, кто может заставить компьютер делать то, что ему заблагорассудится, хочет того сам компьютер или нет.

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

Хотите — верьте, хотите — нет, но эти два значения слова «hack» также связаны. У плохих и оригинальных решений есть нечто общее: они оба идут вразрез с правилами. И существует постепенный переход от нарушения правил, граничащего с безобразностью (использование клейкой ленты для присоединения чего-либо к вашему велосипеду), к такому нарушению правил, что сродни блестяще оригинальному (отказ от Евклидового пространства).

Хакинг зародился еще до появления компьютеров. Работая над «Манхэттенским проектом», Ричард Фейнман развлекался, бывало, тем, что взламывал сейфы с секретными документами. Эта тенденция продолжается и по сей день. Когда мы учились в магистратуре, у одного моего друга хакера, который слишком много времени провел около MIT, был свой собственный набор «отмычек» (сейчас он управляет хеджевым фондом, т.е. занимается деятельностью, которая не так уж и далека от его предыдущих увлечений).

Иногда, сложно объяснить властям, почему кому-то может захотеться совершать такие вещи. У еще одного моего друга были проблемы с правительством из-за взлома компьютеров. И только совсем недавно это объявили преступлением, а ФБР обнаружила, что их обычные методы ведения расследований не работают. Очевидно, что расследование полиции начинается с мотива. Типичных причин преступлений немного: наркотики, деньги, секс, месть. Любознательность не входила у ФБР в список мотивов. На самом деле, сама идея казалась им чуждой.

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

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

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

Я, например, полагаю, что в Голливуде люди просто напросто озадачены отношением хакеров к авторским правам. Это вечная тема горячих дискуссий на Slashdot. Но почему людям, которые программируют компьютеры, следует так переживать по поводу авторских прав, в конце-то концов?!

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

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

image

В 1977 году никто не сомневался в том, что некая группа в IBM разрабатывает то, что, как они ожидали, будет следующим поколением компьютеров для бизнеса. Они ошиблись. Следующее поколение компьютеров для бизнеса разрабатывалось по совершенно другому пути двумя длинноволосыми парнями с одинаковым именем Стив в гараже в Лос Альтосе. В примерно это же время власть имущие объединились для разработки официальной версии операционной системы следующего поколения, Multics. Но двое парней, которые считали Multics чрезмерно сложной, затаились и написали свою. Они дали ей имя с шутливой отсылкой к Multics — Unix.
image

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

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

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

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

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

Хакеры непослушны. В этом вся суть хакинга. И это также является сутью «американности». Не случайно Кремниевая долина находится в Америке, а не во Франции, Германии, Англии или Японии. В тех странах люди не выходят за очерченные рамки.

Я прожил некоторое время во Флоренции. Но после нескольких месяцев пребывания там я понял, что то, что я неосознанно надеялся там найти, находилось в месте, которое я только что покинул. Причина, по которой знаменита Флоренция, заключается в том, что в 1450 году она была Нью-Йорком. В 1450 году ее наполняли непокорные и амбициозные люди, такие, каких сейчас вы встретите в Америке. (Поэтому я и вернулся в Америку).

Довольно существенным преимуществом Америки является то, что она обладает благоприятной средой для нужного вида непокорности, т.е. это родной дом не только для умных, но и для дерзких. А хакеры — это наглые всезнайки, без исключений. Если бы у нас был государственный праздник, это был бы день первого апреля. О нашей работе многое говорит тот факт, что мы используем одно и то же слово как для блестящих, так и для жутко убогих решений. Когда мы размышляем, мы не всегда уверены на 100% какого рода будет это решение. Но как только в нем появляется некоторая неправильность нужного характера, то это многообещающий знак. Странно, что люди воспринимают программирование как точную и систематическую деятельность. Это компьютеры точные и систематические, а хакинг — это то, чем вы занимаетесь непринужденно и с улыбкой.

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

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

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

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

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

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

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

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

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

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

Спасибо Кену Андерсену (Ken Anderson), Тревору Блеквелу (Trevor Blackwell), Даниеле Гиффин (Daniel Giffin), Саре Харлин (Sarah Harlin), Широ Каваи (Shiro Kawai), Джессике Ливингстон (Jessica Livingston), Мэтц (Matz), Джеки МакДоноу (Jackie McDonough), Роберту Моррису (Robert Morris), Эрику Реймонду (Eric Raymond), Гидо ванн Россуму (Guido van Rossum), Девиду Вайнбергеру (David Weinberger), и Стивену Вольфраму (Steven Wolfram) за прочтение черновика данной статьи.



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

image

Hackers and Painters
http://ift.tt/1mRklzD
  1. Why Nerds Are Unpopular
    Their minds are not on the game.
    оригинал, перевод часть 1, часть 2
  2. Hackers and Painters
    Hackers are makers, like painters or architects or writers.
    оригинал, перевод часть 1, часть 2, альтернатива
  3. What You Can't Say
    How to think heretical thoughts and what to do with them.
    оригинал, перевод
  4. Good Bad Attitude
    Like Americans, hackers win by breaking rules.
    оригинал, перевод
  5. The Other Road Ahead
    Web-based software offers the biggest opportunity since the arrival of the microcomputer.
  6. How to Make Wealth
    The best way to get rich is to create wealth. And startups are the best way to do that.
    оригинал, перевод
  7. Mind the Gap
    Could «unequal income distribution» be less of a problem than we think?
    оригинал, перевод
  8. A Plan for Spam
    Till recently most experts thought spam filtering wouldn't work. This proposal changed their minds.
    оригинал, перевод
  9. Taste for Makers
    How do you make great things?
    оригинал, перевод
  10. Programming Languages Explained
    What a programming language is and why they are a hot topic now.
  11. The Hundred-Year Language
    How will we program in a hundred years? Why not start now?
    оригинал, перевод
  12. Beating the Averages
    For web-based applications you can use whatever language you want. So can your competitors.
    орининал, перевод
  13. Revenge of the Nerds
    In technology, «industry best practice» is a recipe for losing.
    оригинал, перевод 1, 2, 3
  14. The Dream Language
    A good programming language is one that lets hackers have their way with it.
    оригинал, перевод часть 1, часть 2
  15. Design and Research
    Research has to be original. Design has to be good.
    оригинал, перевод


(осталось перевести всего 2 главы, кто хочет помочь, присоединяйтесь)

Остальные переводы работ Грэма тут.

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

Готовим ASP.NET 5: подробнее про работу с Gulp

Мы продолжаем нашу колонку по теме ASP.NET 5 публикацией от Дмитрия Сикорского ( DmitrySikorsky) — разработчика из Украины. В этой статье Дмитрий подробнее рассказывает о сценариях применения с ASP.NET 5 популярного средства Gulp. Предыдущие статьи из колонки всегда можно прочитать по ссылке #aspnetcolumn — Владимир Юнев

До появления ASP.NET 5 я никогда не использовал такие инструменты, как Gulp, поэтому пришлось уделить некоторое время и разобраться, что же это такое, когда я создал свой первый проект на этой платформе (правда, тогда там еще был Grunt, но это не важно). Не стану вдаваться в базовые вещи, которые уже и так везде достаточно подробно описаны (подразумеваю, что в вашем проекте уже есть Gulpfile.js и вы можете выполнять задания из него, используя диспетчер выполнения задач Visual Studio 2015), а сразу перейду к делу и на практике покажу, как можно использовать Gulp для автоматизации всего-всего в вашем проекте на ASP.NET 5.

В статье будут приведены фрагменты файла Gulpfile.js тестового проекта AspNet5Gulpization, который целиком лежит тут: http://ift.tt/1PbOYke.

Вступление


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

Подготовка

aspnetcolumngithubСовет! Вы можете попробовать все самостоятельно или загрузив исходный код из GitHub http://ift.tt/1PbOYke.

Для начала, нам необходимо перечислить в нашем Gulp-файле все пакеты, которые мы будем использовать в своих заданиях (и убедиться, что все они присутствуют в package.json):
var gulp = require("gulp"),
  autoprefixer = require("gulp-autoprefixer"),
  concat = require("gulp-concat"),
  del = require("del"),
  minifyCss = require("gulp-minify-css"),
  rename = require("gulp-rename"),
  runSequence = require("run-sequence"),
  sass = require("gulp-sass"),
  tsc = require("gulp-tsc"),
  uglify = require("gulp-uglify");

Далее, очень удобно представлять пути где лежат исходные и результирующие файлы в виде объекта, чтобы иметь возможность редактировать их все в одном месте:
var paths = {
  frontend: {
    scss: {
      src: [
        "styles/*.scss"
      ],
      dest: "wwwroot/css"
    },
    ts: {
      src: [
        "scripts/*.ts"
      ],
      dest: "wwwroot/js"
    }
  },
  shared: {
    bower: {
      src: "bower_components",
      dest: "wwwroot/lib"
    }
  }
}

И наконец, опишем основную Gulp-задачу, которая будет производить перестроение всех скриптов и стилей, их обработку и копирование в результирующую папку:
gulp.task(
  "rebuild",
  function (cb) {
    runSequence(
      "clean",
      "build",
      "minify",
      "delete-unminified",
      "rename-temp-minified",
      "delete-temp-minified",
      cb
    );
  }
);

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

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

Скрипты


Я очень любил (и продолжаю) JavaScript за его простоту и изящество. Теперь я полюбил еще и TypeScript. (Это замечательный инструмент, рекомендую обратить на него внимание.) Все ts-файлы я обычно храню в папке Scripts, которая игнорируется при публикации проекта. Это исходники клиентских скриптов. Я настроил несколько заданий в моем Gulp-файле, которые сначала компилируют TypeScript в JavaScript, затем сжимает его, затем склеивают в один файл и, наконец, копируют полученный файл в папку wwwroot/js, откуда он и используется в приложении. (Если вы не используете TypeScript, то можно просто пропустить этап его превращения в JavaScript – остальные задания будут работать без изменений.)

По умолчанию, Visual Studio 2015 компилирует ts-файлы в момент их сохранения и складывает полученные js-файлы в ту же папку. Нам это поведение не нужно, поэтому отключаем компиляцию TypeScript в настройках проекта.

Вот так выглядит задание, компилирующее TypeScript:

gulp.task(
  "frontend:build-ts", function (cb) {
    return gulp.src(paths.frontend.ts.src)
      .pipe(tsc())
      .pipe(gulp.dest(paths.frontend.ts.dest));
  }
);

А вот так можно сжать полученный JavaScript и склеить его в один результирующий файл:
gulp.task(
  "frontend:minify-js", function (cb) {
    return gulp.src(paths.frontend.ts.dest + "/*.js")
      .pipe(uglify())
      .pipe(concat("aspnet5gulpization.min.js.temp"))
      .pipe(gulp.dest(paths.frontend.ts.dest));
  }
);

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

Кстати, если вдруг при восстановлении пакетов NPM вы получаете сообщение об ошибке (опционально, с формулировкой, состоящей из бессвязной последовательности символов), или же Visual Studio 2015 просто падает при попытке восстановить пакеты, возможно это связано с глубиной вложенности вашего проекта в файловой системе. (Частично, я нашел информацию об этом тут: http://ift.tt/1PggsqC.) Потратив некоторое время, я просто создал пустой проект в менее вложенной папке, скопировал туда свой package.json, восстановил там пакеты и затем перенес их вместе с папкой node_modules в свой проект. Также, в процессе падения студии в папку npm-cache может попасть испорченный пакет, поэтому стоит иметь это в виду и при необходимости его оттуда удалить.

Стили


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

Аналогично TypeScript, мой Gulp-файл содержит задания для компиляции SCSS в CSS, добавления вендорных префиксов, сжатия, склейки и копирования полученного файла в папку wwwroot/css.

Компиляция SCSS в CSS выглядит следующим образом:

gulp.task(
  "frontend:build-scss", function (cb) {
    return gulp.src(paths.frontend.scss.src)
      .pipe(sass())
      .pipe(gulp.dest(paths.frontend.scss.dest));
  }
);

Ну и сжатие, склейка (с одновременной расстановкой вендорных префиксов):
gulp.task(
  "frontend:minify-css", function (cb) {
    return gulp.src(paths.frontend.scss.dest + "/*.css")
      .pipe(minifyCss())
      .pipe(autoprefixer("last 2 version", "safari 5", "ie 8", "ie 9"))
      .pipe(concat("aspnet5gulpization.min.css.temp"))
      .pipe(gulp.dest(paths.frontend.scss.dest));
  }
);

Библиотеки


Если в проекте на ASP.NET 5 вам потребуется, например, JQuery, скорее всего вы загрузите его при помощи Bower, и, в отличии от NuGet, который использовался ранее, вы получите немного больше, чем просто файл jquery.min.js и парочки других. В папке bower_components будет создана папка jquery, в которой, кроме упомянутого выше файла, будет несжатый вариант библиотеки, а также ее исходники (которые, само собой, при публикации будут игнорироваться).

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

Во-первых, можно просто подключить на страницу файл jquery.min.js, предварительно скопировав его в папку wwwroot/lib/jquery. Так поступил я (не знаю, возможно правильнее использовать сервисы, вроде Google Hosted Libraries, чтобы в некоторых случаях браузер брал закешированный при посещении другого сайта вариант библиотеки, а не скачивал ее вновь, но чаще всего я так не делаю).

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

Вот задание, копирующее необходимые файлы для трех библиотек:

gulp.task(
  "lib-copy", function (cb) {
    var lib = {
      "/jquery": "/jquery/dist/jquery*.{js,map}",
      "/jquery-validation": "/jquery-validation/dist/jquery.validate*.js",
      "/jquery-validation-unobtrusive": "/jquery-validation-unobtrusive/jquery.validate.unobtrusive*.js"
    };

    for (var $package in lib) {
      gulp
        .src(paths.shared.bower.src + lib[$package])
        .pipe(gulp.dest(paths.shared.bower.dest + $package));
    }

    cb();
  }
);

Выводы


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

Авторам


Друзья, если вам интересно поддержать колонку своим собственным материалом, то прошу написать мне на vyunev@microsoft.com для того чтобы обсудить все детали. Мы разыскиваем авторов, которые могут интересно рассказать про ASP.NET и другие темы.

Об авторе


Дмитрий Сикорский,
Украина
DmitrySikorsky

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

Укрощаем UEFI SecureBoot

Данные обещания надо выполнять, тем более, если они сделаны сначала в заключительной части опуса о безопасности UEFI, а потом повторены со сцены ZeroNights 2015, поэтому сегодня поговорим о том, как заставить UEFI SecureBoot работать не на благо Microsoft, как это чаще всего настроено по умолчанию, а на благо нас с вами.
Если вам интересно, как сгенерировать свои обственные ключи для SecureBoot, как установить их вместо стандартных (или вместе с ними), как подписать ваш любимый EFI-загрузчик, как запретить загрузку неподписанного или подписанного чужими ключами кода, как выглядит интерфейс для настройки SecureBoot у AMI, Insyde и Phoenix и почему это, по большому счету, совершенно не важно — добро пожаловать под кат, но опасайтесь большого количества картинок и длинных консольных команд.

Введение


О том, как устроен и работает SecureBoot, я уже рассказывал в начале пятой части вышеупомянутого опуса, повторяться смысла не вижу. Если вы не в курсе, о чем весь этот UEFI SecureBoot вообще, кто такие PK, KEK, db и dbx, и почему с точки зрения SecureBoot по умолчанию хозяином вашей системы является производитель UEFI, а единственным авторизованным пользователем является Microsoft — смело проследуйте туда, мы вас тут пока подождем.

С ликбезом закончили, теперь к делу. Несмотря на то, что про создание своих ключей и настройку SecureBoot написано за три последних года с десяток отличных статей (ссылки на часть из которых приведены в разделе Литература), воз и ныне там. Основная проблема с информацией о настройке SecureBoot даже в англоязычном сегменте сети (не говоря уже о рунете) — большая часть статей, текстов и постов обрывается на «вот у нас теперь есть ключи в формате EFI Signature List, добавьте их зависимым от вашего вендора прошивки способом и готово». При этом сами вендоры не торопятся описывать меню настройки SecureBoot ни в документации на свои платформы для OEM'ов, ни в мануалах на конечные системы, в результате пользователь теряется на незнакомой местности и либо отключает SecureBoot, мешающий загружать его любимую OpenBSD (или что там у него), либо оставляет его на настройках по умолчанию (а чего, Windows грузится же). Именно этот последний шаг я и попытаюсь описать более подробно, но не в ущерб остальным необходимым шагам.

Тестовая конфигурация


Специально для этой статьи я достал из закромов пару не самых новых ноутбуков с прошивками на платформах Phoenix SCT и Insyde H2O, а также совершенно новую плату congatec (разработкой прошивки для которой я занят в данный момент) на платформе AMI AptioV. Встречайте, наши тестовые стенды:

1. AMI, они же "треугольные": congatec conga-TR3 @ conga-TEVAL, AMD RX-216GD (Merlin Falcon), AMI AptioV (UEFI 2.4)

2. Insyde, они же "квадратные": Acer Aspire R14 R3-471T (Quanta ZQX), Intel Core i3-4030U (Ivy Bridge), Insyde H2O (UEFI 2.3.1C)

3. Phoenix, они же "полукруглые": Dell Vostro 3360 (Quanta V07), Intel Core i7-3537U (Ivy Bridge), Phoenix SCT (UEFI 2.3.1C)

Об интерфейсах для настройки SecureBoot


На всех вышеперечисленных системах производитель заявляет о поддержке технологии UEFI SecureBoot, но интерфейс для ее настройки сильно отличается между системами. К счастью, это не очень большая проблема, поскольку для настройки SecureBoot на совместимых со спецификацией UEFI 2.3.1C (и более новых) прошивках никакого интерфейса в Setup, кроме возможности удаления текущего PK (т.е. перевода SecureBoot в так называемый Setup Mode) не требуется, а после этого можно использовать любое EFI-приложение, способное вызвать UEFI-сервис gRS->SetVariable с предоставленными пользователем данными, в том числе утилиту KeyTool.efi из пакета efitools, которая специально для управления ключами и предназначена, осталось только научиться ей пользоваться.
Тем не менее, если удобный интерфейс для настройки присутствует (у AMI он, на мой взгляд, даже удобнее KeyTool'а) — можно воспользоваться им, так что рассказывать про эти самые интерфейсы все равно придется.
Пара слов про скриншоты UEFI
Благодаря универсальности GOP, ConIn/ConOut и DevicePath можно было сесть и написать за полчаса простой DXE-драйвер, который снимал бы замечательные скриншоты в формате BMP со всего, что происходит в графической консоли после его (драйвера) загрузки по нажанию горячей клавиши, после чего сохранял бы их на первом попавшемся USB-носителе с ФС FAT32… Но его нужно сначала написать, потом отладить, потом интегрировать в прошивки так, чтобы они от этого не развалились (а на ноутбуках придется микросхему с прошивкой выпаивать и под программатор класть, если вдруг что-то не так пойдет), плюс с подконтрольного мне AptioV можно снимать скриншоты просто используя терминал и console serial redirection, а у остальных там настроек буквально на два-три экрана, которые можно банально с монитора сфотографировать, поэтому прошу вас, уважаемые читатели, простить вашего покорного слугу за эти кривые фотографии и за тот факт, что он — ленивая жопа.

Готовим плацдарм


Начнем с лирического отступления о наличии нужного софта для разных ОС. Несмотря на то, что Microsoft является одним из разработчиков технологии, в открытом доступе до сих пор отсутствуют нормальные средства для работы с ней из Windows (ключи можно сгенерировать утилитой MakeCert из Windows SDK, а для всего остального предлагается использовать HSM третьих лиц за большие деньги). Я подумывал сначала взять и написать нужную утилиту на Qt, но потому решил, что ключи и подписи каждый день генерировать не нужно, а на пару раз хватит и существующих решений. Можете попробовать переубедить меня в комментариях, если хотите.
В общем, для всего нижеперечисленного вам понадобится Linux (который можно запустить с LiveUSB, если он у вас не установлен). Для него существует целых два набора утилит для работы с SecureBoot: efitools/sbsigntool и EFIKeyGen/pesign. У меня есть положительный опыт работы с первым набором, поэтому речь пойдет именно о нем.
В итоге, кроме Linux, нам понадобятся несколько вещей:
1. Пакет openssl и одноименная утилита из него для генерирования ключевых пар и преобразования сертификатов из DER в PEM.
2. Пакет efitools, а точнее утилиты cert-to-efi-sig-list, sign-efi-sig-list для преобразования сертификатов в формат ESL и подписи файлов в этом формате, и KeyTool.efi для управления ключами системы, находящейся в SetupMode.
3. Пакет sbsigntool, а точнее утилита sbsign для подписи исполняемых файлов UEFI (т.е. загрузчиков, DXE-драйверов, OptionROM'ов и приложений для UEFI Shell) вашим ключем.
Загрузите Linux, установите вышеуказанные пакеты, откройте терминал в домашней директории и переходите к следующему шагу.

Генерируем собственные ключи для SecureBoot


Как обычно, есть несколько способов сделать что-либо, и чем мощнее используемый инструмент, тем таких способов больше. OpenSSL же как инструмент развит настолько, что кажется, что он умеет делать вообще всё, а если почитать к нему man — то и абсолютно всё остальное. Поэтому в этой статье я ограничусь непосредственной генерацией ключевых файлов, а танцы вокруг создания собственного CA оставлю на самостоятельное изучение.
Генерируем ключевые пары
Ключей понадобится сгенерировать три штуки: PK, KEK и ISK.
Начнем с PK, для генерации которого нужно выполнить следующее
openssl req -new -x509 -newkey rsa:2048 -sha256 -days 365 -subj "/CN=Platform Key" -keyout PK.key -out PK.pem
после чего ввести и подтвердить пароль, который потом спросят при попытке подписи чего-либо получившимся закрытым ключом.
Командой выше мы просим OpenSSL сгенерировать нам ключевую пару RSA2048/SHA256 со сроком действия на один год, под названием Platform Key, с выводом закрытого ключа в файл PK.key, а открытого — в файл PK.pem. Если добавить -nodes, то для подписи этой ключевой парой не нужен будет пароль, но здесь мы этого делать не станем — с паролем хоть ненамного, но безопаснее.
Таким же образом генерируем ключевые пары для KEK и ISK, пароли при этом советую вводить разные:
openssl req -new -x509 -newkey rsa:2048 -sha256 -days 365 -subj "/CN=Key Exchange Key" -keyout KEK.key -out KEK.pem
openssl req -new -x509 -newkey rsa:2048 -sha256 -days 365 -subj "/CN=Image Signing Key" -keyout ISK.key -out ISK.pem

Конвертируем открытые ключи в формат ESL
Теперь нужно сконвертировать открытые ключи из формата PEM в понятный UEFI SecureBoot формат ESL. Этот бинарный формат описан в спецификации UEFI (раздел 30.4.1 в текущей версии 2.5) и интересен тем, что файлы в нем можно соединять друг с другом конкатенацией, и этот факт нам еще пригодится.
cert-to-efi-sig-list -g "$(uuidgen)" PK.pem PK.esl
cert-to-efi-sig-list -g "$(uuidgen)" KEK.pem KEK.esl
cert-to-efi-sig-list -g "$(uuidgen)" ISK.pem ISK.esl
Ключ -g добавляет к сгенерированному ESL-файлу GUID, в нашем случае — случайный, полученый запуском утилиты uuidgen и использованием ее вывода. Если этой утилиты у вас нет — придумывайте GUIDы сами или оставьте значение по умолчанию.
Подписываем ESL-файлы
Для правильно работы SecureBoot необходимо, чтобы PK был подписан сам собой, KEK подписан PK, а хранилища db и dbx — сответственно KEK. При этом PK не может быть несколько, а вот ситуация с несколькими KEK хоть и встречается в дикой природе, но я все же настоятельно рекомендую удалить предустановленный ключ Microsoft из KEK по простой причине — db и dbx могут быть подписаны любым ключом из хранилища KEK, т.е. если ключ MS оттуда не удалить, то у MS будет возможность управлять содержимым db и dbx, т.е. добавлять любые новые ключи или хеши в список доверенной загрузки и удалять из него существующие. На мой взгляд, это немного слишком, и если мы берем управление ключами в свои руки, то нужно делать это до конца.
Если вы думаете иначе
Ну тогда вам прямая дорога вот сюда, там в самом конце раздела 1.3.4.3 есть ссылка на сертификат Microsoft Corporation KEK CA 2011 в формате DER, из которого нужно сначала получить PEM командой
openssl x509 -in MicCorKEKCA2011_2011-06-24.crt -inform DER -out MsKEK.pem -outform PEM
затем сконвертировать полученный PEM в ESL командой
cert-to-efi-sig-list -g "$(uuidgen)" MsKEK.pem MsKEK.esl
после чего добавить получившийся файл к нашему файлу KEK.esl командой
cat KEK.esl MsKEK.esl > KEK.esl
Теперь Microsoft такой же авторизованный пользователь вашей платформы, как и вы сами, с чем я вас и поздравляю.
С другой стороны, если вы не хотите терять возможность загрузки Windows и подписанных ключом Microsoft исполняемых компонентов (к примеру, GOP-драйверов внешних видеокарт и PXE-драйверов внешних сетевых карточек), то к нашему ISK.esl надо будет добавить еще пару ключей — ключ Microsoft Windows Production CA 2011, которым MS подписывает собственные загрузчики и ключ Microsoft UEFI driver signing CA, которым подписываются компоненты третьих сторон (именно им, кстати, подписан загрузчик Shim, с помощью которого теперь стартуют разные дистрибутивы Linux, поддерживающие SecureBoot из коробки).
Последовательность та же, что и под спойлером выше. Конвертируем из DER в PEM, затем из PEM в ESL, затем добавляем к db.esl, который в конце концов надо будет подписать любым ключом из KEK:
openssl x509 -in MicWinProPCA2011_2011-10-19.crt -inform DER -out MsWin.pem -outform PEM
openssl x509 -in MicCorUEFCA2011_2011-06-27.crt -inform DER -out UEFI.pem -outform PEM
cert-to-efi-sig-list -g "$(uuidgen)" MsWin.pem MsWin.esl
cert-to-efi-sig-list -g "$(uuidgen)" UEFI.pem UEFI.esl
cat ISK.esl MsWin.esl UEFI.esl > db.esl

Теперь подписываем PK самим собой:
sign-efi-sig-list -k PK.key -c PK.pem PK PK.esl PK.auth
Подписываем KEK.esl ключем PK:
sign-efi-sig-list -k PK.key -c PK.pem KEK KEK.esl KEK.auth
Подписываем db.esl ключем KEK:
sign-efi-sig-list -k KEK.key -c KEK.pem db db.esl db.auth

Если еще не надоело, можно добавить чего-нибудь еще в db или создать хранилище dbx, нужные команды вы теперь знаете, все дальнейшее — на ваше усмотрение.
Подписываем загрузчик
Осталось подписать какой-нибудь исполняемый файл ключом ISK, чтобы проверить работу SecureBoot после добавления ваших ключей. Для тестов я советую подписать утилиту RU.efi, она графическая и яркая, и даже издалека видно, запустилась она или нет. На самом деле, утилита эта чрезвычайно мощная и ей можно натворить добрых и не очень немало дел, поэтому после тестов лучше всего будет её удалить, и в дальнейшем подписывать только загрузчики.
В любом случае, подписываются исполняемые файлы вот такой командой:
sbsign --key ISK.key --cert ISK.pem --output bootx64.efi RU.efi
Здесь я заодно переименовал исполняемый файл в bootx64.efi, который нужно положить в директорию /EFI/BOOT тестового USB-носителя с ФС FAT32. Для 32-битных UEFI (избавь вас рандом от работы с ними) используйте bootia32.efi и RU32.efi.

В результате вот этого всего у вас получились три файла .auth, которые нужно будет записать «как есть» в NVRAM-переменные db, KEK и PK, причем именно в таком порядке. Скопируйте все три файла в корень другого USB-носителя с ФС FAT32, на котором в качестве /EFI/BOOT/bootx64.efi выступит /use/share/efitools/efi/KeyTool.efi (скопируйте его еще и в корень, на всякий случай) и ваш «набор укротителя SecureBoot» готов.

Укрощение строптивого


Начинается все одинаково: вставляем нашу флешку с ключами и KeyTool'ом в свободный USB-порт, включием машину, заходим в BIOS Setup.
Здесь, прежде чем заниматься настройкой SecureBoot, нужно отключить CSM, а с ним — и легаси-загрузку, с которыми наша технология не совместима. Также обязательно поставьте на вход в BIOS Setup пароль подлиннее, иначе можно будет обойти SecureBoot просто отключив его, для чего на некоторых системах с IPMI и/или AMT даже физическое присутсвие не потребуется.
AMI AptioV
У большинства прошивок, основанных на коде AMI, управление технологией SecureBoot находится на вкладке Security, у меня это управление выглядит так:

Заходим в меню Key Management (раньше оно было на той же вкладке, сейчас его выделили в отдельное) и видим там следующее:

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

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

Далее нужно устройство и файл на нем, а затем выбрать формат этого файла, в нашем случае это Authenticated Variable:

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

Повторяем то же самое для KEK и PK, и получам на выходе вот такое состояние:

Все верно, у нас есть единственный PK, всего один ключ в KEK и три ключа в db, возвращаемся в предыдущее меню кнопкой Esc и включаем SecureBoot:

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

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

Вот теперь можно сказать, что SecureBoot работает как надо.
Если у вашего AMI UEFI такого интерфейса для добавления ключей нет, то вам подойдет другой способ, о котором далее.
Insyde H2O
Здесь все несколько хуже, чем в предыдущем случае. Никакого интерфейса для добавления собственных ключей нет, и возможностей настройки SecureBoot предлагается всего три: либо удалить все переменные разом, переведя SecureBoot в Setup Mode, либо выбрать исполняемый файл, хеш которого будет добавлен в db, и его можно будет запускать даже в том случае, если он не подписан вообще, либо вернуться к стандартным ключам, в качестве которых на этой машине выступают PK от Acer, по ключу от Acer и MS в KEK и куча всякого от Acer и MS в db.
Впрочем, нет интерфейса — ну и черт с ним, у нас для этого KeyTool есть, главное, что в Setup Mode перейти можно. Интересно, что BIOS Setup не дает включить SecureBoot, если пароль Supervisor Password не установлен, поэтому устанавливаем сначала его, затем выполняем стирание ключей:

После чего на соседней вкладке Boot выбираем режим загрузки UEFI и включаем SecureBoot:

Т.к. фотографии у меня посреди ночи получаются невыносимо отвратительными, то скриншоты KeyTool'а я сделаю на предыдущей системе, и придется вам поверить в то, что на этой все выглядит точно также (мамой клянусь!).
Загружаемся с нашего носителя в KeyTool, и видим примерно следующее:

Выбираем Edit Keys, попадаем в меню выбора хранилища:

Там сначала выбираем db, затем Replace Keys, затем наше USB-устройство, а затем и файл:

Нажимаем Enter и без всяких сообщений об успехе нам снова показывают меню выбора хранилища. Повторяем то же самое сначала для KEK, а затем и для PK, после выходим в главное меню двойным нажатием на Esc. Выключаем машину, включаем заново, пытаемся загрузить KeyTool снова и видим такую картину (которую я утащил из дампа прошивки, ее фото на глянцевом экране еще кошмарнее, чем предыдущие):

Ну вот, одна часть SecureBoot'а работает, другая проверяется запуском подписанного нами RU.efi и тоже работает. У меня на этой системе Windows 8 установлена в UEFI-режиме, так вот — работает и она, Microsoft с сертификатом не подвели.
Phoenix SCT
Здесь возможностей еще меньше, и во всем меню Secure Boot Configuration на вкладке Security всего два пункта: возврат к стандартным ключам и удаление всех ключей с переводом системы в SetupMode, нам нужно как раз второе:

Затем на вкладке Boot нужно выбрать тип загрузки UEFI, включить SecureBoot, и создать загрузочную запись для KeyTool'а, иначе на этой платформе его запустить не получится:

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

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

Заключение


В итоге, благодаря утилитам с открытым кодом, удалось завести SecureBoot на системах с UEFI трех различных вендоров, сгенерировать свои собственные ключи и подписать ими нужные нам исполняемые файлы. Теперь загрузка платформы целиком в наших руках, но только в случае, если пароль на BIOS стойкий и не хранится открытым текстом, как у некоторых, а в реализации SecureBoot нет каких-либо известных (или еще неизвестных) дыр. Сам по себе SecureBoot — не панацея от буткитов, но с ним ситуация с безопасной загрузкой все равно намного лучше, чем без него.
Надеюсь, что материал вам поможет, и спасибо за то, что прочитали эту портянку.

Литература


Managing EFI Bootloaders for Linux: Controlling SecureBoot.
AltLinux UEFI SecureBoot mini-HOWTO.
Booting a self-signed Linux kernel.
Sakaki's EFI Install Guide: Configuring SecureBoot.
Ubuntu Security Team: SecureBoot.
Owning your Windows 8 UEFI Platform.
MinnowBoard Max: Quickstart UEFI Secure Boot.
Windows 8.1 Secure Boot Key Creation and Management Guidance.

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

Facebook угрожает специалисту по безопасности, взломавшему Instagram

Независимый специалист по безопасности Уэсли Вайнберг (Wesley Wineberg) подвергся серьёзному давлению со стороны компании Facebook. Она не заплатила за найденные уязвимости на сервере Instagram, да ещё угрожает судебным иском.

Уэсли нашёл уязвимость в инфраструктуре Instagram, с помощью которой скачал практически всё ценное, что есть на серверах Instagram: исходный код последней версии, SSL-сертификаты и приватные ключи для Instagram.com, ключи для подписи куков аутентификации, учётные данные от почтового сервера и ключи для некоторых других продуктов, в том числе для подписи мобильных приложений под iOS и Android.


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

Вся эта эпопея началась ещё в октябре с того, что коллега Вайнберга сообщил ему об открытом веб-сервере http://ift.tt/1PaD9dY, который работает на инстансе Amazon EC2 и крутит фреймворк для мониторинга Sensu. О баге с открытой админкой сервера в Facebook уже сообщили, но коллега намекнул, что заметил там баг с удалённым сбросом пароля в приложении Ruby on Rails (CVE-2013-3221), подробнее см. статью о способах атаки на Ruby on Rails.

Хакер предположил, что такую же уязвимость можно найти в другом коде Instagram. Он изучил их репозиторий на Github, но ничего такого не обнаружил. Но зато нашёл кое-что получше. В файле secret_token.rb на гитхабе был прошит секретный токен Rails. В статье по ссылке выше подробно описано, как с помощью такого токена не только сфабриковать сессионные куки, но и инициировать десериализацию сессионных куков в Rails, чтобы напрямую запустить удалённое исполнение кода.

Уэсли сконфигурировал локальный инстанс Rails и воспользовался эксплоитом, который лежит на гитхабе: rails-3.2.10-remote-code-execution.md, чтобы сгенерировать объект, который спрячет в куки.

Полученный объект он подписал секретным ключом от Sensu-Admin — и получил куки от Sensu-Admin. К радости исследователя, сервер принял куки, запустил десериализацию, подтвердил подпись и запустил на исполнение объект, спрятанный внутри.

Это была команда wget http://ift.tt/1lW8I1Y — и сервер sensu.instagram.com послушно обратился к хакерскому серверу, что явно указало на то, что эксплоит работает.

Имея на руках работающий RCE, хакер запустил удалённый шелл.

Получив полное подтверждение бага, 21 октября 2015 года Вайнберг сообщил о двух уязвимостях в Facebook, рассчитывая на вознаграждение. В своём блоге он вспоминает статью 2012 года в Bloomberg, где руководитель отдела безопасности Facebook рассказывает о своей программе вознаграждений за найденные уязвимости и говорит: «Если найдут баг на миллион долларов, мы выплатим его».

Чтобы доказать всю серьёзность бага, исследователь продолжил изучать содержимое сервера Instagram, в том числе скачал к себе локальную базу Postgres с информацией об аккаунтах 60 сотрудников и паролями, захешированными bcrypt. Такие хеши очень трудно подобрать: у хакера на компьютере брутфорс шёл со скоростью всего 250 попыток в секунду. На удивление, некоторые пароли оказались настолько лёгкими, что атака по словарю дала эффект уже через несколько минут.

  • Шесть паролей "changeme"
  • Три пароля совпадали с именем пользователя
  • Два пароля "password"
  • Один пароль "instagram"

Отойдя от шока, хакер выбрал один из паролей и залогинился в веб-интерфейс.

После этого он отправил в Facebook информацию о слабых пользовательских аккаунтах (22 октября).

Ожидая положенное вознаграждение от Facebook, специалист изучил содержимое конфигурационного файла /etc/sensu/config.json, там были ключевые пары от 82 контейнеров Amazon S3. Доступ был закрыт ко всем, кроме одного. Но в этом единственном контейнере он нашёл ещё одну ключевую пару, которая давала доступ ко всем 82-м остальным контейнерам.

Facebook оперативно отреагировал, спрятал сервер Sensu за файрвол и 16 ноября выплатил вознаграждение $2500 за первый из трёх багов. В то же время 28 октября Вайнберг получил письмо, где ему отказали в выплате вознаграждения за второй и третий баги, потому что уязвимость со слабыми пользовательскими аккаунтами «выходит за рамки действия программы вознаграждения за уязвимости».

Разумеется, специалист немного обиделся. Получить всего $2500 за уязвимость с удалённым исполнением кода — это почти оскорбительно мало. Он опубликовал в своём блоге описание взлома и несколько почтовых писем из переписки с отделом безопасности Facebook.

После этого ситуация начала накаляться. Директор по безопасности Facebook Алекс Стамос позвонил директору компании Synack, с которой Вайнберг работает по контракту. Он сказал, что произошёл несанкционированный доступ к базе данных сотрудников Instagram и конфиденциальной информации пользователей. Эта информация должна быть немедленно удалена. Стамос сказал, что не хочет сообщать юристам об инциденте, но в случае необходимости процесс против Вайнберга будет запущен.

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

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

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

пятница, 18 декабря 2015 г.

[recovery mode] Бэкдоры в файрволах Juniper

сегодня в 21:31

Если кто-то еще не слышал, Juniper опубликовал заявление: обнаружен код в ScreenOS версий с 6.2.0r15 по 6.2.0r18 и с 6.3.0r12 по 6.3.0r20, позволяющий обладающим соответствующей информацией лицам сделать две вещи:

1) Аутентифицироваться на устройстве по ssh
2) Слушать VPN трафик

Обнаружить проникновение может быть непросто.

Затронута только ScreenOS. SRX в порядке. Наверное.

Подробнее:
http://ift.tt/1ObEOmp
http://ift.tt/1ObVKt7

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

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

Сравниваем swift и rust


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

Основы синтаксиса

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

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

rust

fn main() {
    let i:int32 = 16;
    let mut f = 8.0;
    f *= 2.0;

    println!("Hello rust! within vars {} and {}", i, f);
}

swift

let i:Int = 10
var f = 15.0
f /= 2

print("Hello swift within vars \(i) \(f)")

Можно заметить, что let и там и там означает одно и тоже, а swift var аналогичен rust let mut. Но имеются и различия: в rust во всех числовых типах однозначно указывается размер, а swift же следует традициям Си. В этом вопросе rust выглядит более низкоуровневым.
Интерполяция строк в rust'е делается при помощи макроса format!, который вызывается в недрах println!, а в swift'е же это фича самого языка, но при этом я не нашел в документации способов задать опции форматирования.
Интересна разница в трактовании ";": для rust'а это признак окончания выражения, что позволяет делать некоторые изящные вещи, например последнее выражение внутри функции автоматически становится возвращаемым значением. Swift же просто следует традициям pascal'я, где точка с запятой просто разделяет операторы, находящиеся на одной строке.

Теперь давайте попробуем создать функцию, которая принимает функцию от двух аргументов и преобразует ее в функцию от одного:

rust

fn apply<F: 'static>(f: F, v1: i32) -> Box<Fn(i32) -> ()>
    where F: Fn(i32, i32) -> ()
{
    Box::new(move |v2| f(v1, v2))
}

fn print_sum(a: i32, b: i32) -> ()
{
    println!("Rust: sum a and b is {}", a + b);
}

fn main() {
    let a = 2; let b = 5;
    print_sum(a, b);

    let f = print_sum;
    let f2 = apply(f, b);
    f2(a);
}

swift

func apply(f: (_: Int, _: Int) -> (), _ v1: Int) -> (_: Int) -> ()
{
    return {(c: Int) -> () in
        return f(v1, c)
    }
}

func print_sum(a: Int, second b: Int) -> ()
{
    print("Swift: sum a and b is \(a+b)")
}

let a = 2; let b = 5;
print_sum(a, second:b)

let f2 = apply(print_sum, b)
f2(a)

Здесь уже явно заметны различия, я оставлю за бортом чисто синтаксические отличия типа внешних имен у именованных параметров в swift или обобщений в rust, и перейду к рассмотрению более существенной разницы. Подход swift заметно более высокоуровневый: компилятор сам решает каким образом хранить нашу результирующую функцию, для rust же пришлось её явно упаковывать в box, потому, что moved замыкания являются безразмерным типом.
Давайте проверим, чьи же лямбда функции работают быстрее:

Код бенчмарков
rust
fn apply<F: 'static>(f: F, v1: i32) -> Box<Fn(i32) -> i32>
    where F: Fn(i32, i32) -> i32
{
    Box::new(move |v2| f(v1, v2))
}

fn make_sum(a: i32, b: i32) -> i32
{
    a + b
}

fn main() {
    let a = 2; let b = 5;
    let c = make_sum(a, b);
    println!("Rust: c is {}", c);

    let f2 = apply(make_sum, b);

    let mut d = 0;
    for i in 0..1000000000 {
        d = f2(i);
    }

    println!("Rust: d is {}", d);
}


swift

func apply(f: (_: Int, _: Int) -> Int, _ v1: Int) -> (_: Int) -> Int
{
    return {(c: Int) -> Int in
        return f(v1, c)
    }
}

func make_sum(a: Int, second b: Int) -> Int
{
    return a + b
}

let a = 2; let b = 5;
let c = make_sum(a, second:b)
print("Swift: c is \(c)")

let f2 = apply(make_sum, b)
f2(a)

var d = 0;
for i in 0...1000000000 {
    d = f2(i);
}

print("Swift: d is \(d)");

Итоговый результат: 4.0 секунды у rust против 1.17 у swift. Получается, что в случае более абстрактного кода у компилятора появляется больше возможностей для оптимизации, но на канале ruRust/general мне подсказали способ, который может и не выглядит так красиво, но зато позволяет оптимизатору выложится по полной. В конечном счете Rust посчитал весь цикл прямо во время компиляции, что очень круто.
В умелых руках rust позволяет творить чудеса.

Код быстрой версии
struct Curry<'a> {
    f: &'a Fn(i32, i32) -> i32,
    v1: i32
}

impl<'a> Curry<'a> {
    fn new<F: 'static>(f: &'a F, v1: i32) -> Curry<'a> where F: Fn(i32, i32) -> i32  {
        Curry { f: f, v1: v1 }
    }

    fn call(&'a self, v2: i32) -> i32 {
        (*self.f)(self.v1, v2)
    }
}

fn make_sum(a: i32, b: i32) -> i32
{
    a + b
}

fn main() {
    let a = 2; let b = 5;
    let c = make_sum(a, b);
    println!("Rust: c is {}", c);

    let borrow = &make_sum;
    let f2 = Curry::new(borrow, b);

    let mut d = 0;
    for i in 0..1000000000 {
        d = f2.call(i);
    }

    println!("Rust: d is {}", d);
}


Перечисления и сопоставление с образцом:

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

rust

enum MathOperation {
    Value(i32),
    Sum(Box<MathOperation>, Box<MathOperation>),
    Mul(Box<MathOperation>, Box<MathOperation>)
}

trait HasName {
    fn name(&self) -> &'static str;
}

impl HasName for MathOperation {
    fn name(&self) -> &'static str {
        match *self {
            MathOperation::Value(_) => "Value",
            MathOperation::Sum(_,_) => "Sum",
            MathOperation::Mul(_,_) => "Mul"
        }
    }
}

impl MathOperation {
    fn solve(&self) -> i32 {
        match *self {
            MathOperation::Value(i)         => i,
            MathOperation::Sum(ref left, ref right) => left.solve() + right.solve(),
            MathOperation::Mul(ref left, ref right) => left.solve() * right.solve()
        }
    }
}

fn main() {
    let op = MathOperation::Sum(Box::new(MathOperation::Value(10)),
                                Box::new(MathOperation::Mul(Box::new(MathOperation::Value(20)),
                                                            Box::new(MathOperation::Value(2)))));
                                
    ;
    println!("Rust: op is {} solved {}", op.name(), op.solve());
}


Swift

enum MathOperation {
  case Value(Int)
  indirect case Sum(MathOperation, MathOperation)
  indirect case Mul(MathOperation, MathOperation)
  
  func solve() -> Int {
    switch self {
    case .Value(let value):
        return value
    case .Sum(let left, let right):
        return left.solve() + right.solve()
    case .Mul(let left, let right):
        return left.solve() * right.solve()
    }
  }
}

protocol HasName {
  func name() -> String;
}

extension MathOperation : HasName
{
  func name() -> String {
    switch self {
      case .Value(_):
        return "Value"
      case .Sum(_, _):
        return "Sum"
      case .Mul(_, _):
        return "Mul"
      }
  }
}

let op = MathOperation.Sum(MathOperation.Value(10),
                           MathOperation.Mul(MathOperation.Value(20),
                                             MathOperation.Value(2)))
                                             
print("Swift: op is \(op.name()) solved \(op.solve())");


Мы можем заметить, что в Rust для организации рекурсивных перечислений потребовалось упаковать их в контейнер Box, потому, что их размер может быть произвольным и мы не можем знать на этапе компиляции сколько нам для этого потребуется памяти. В swift для обозначения рекурсивных перечислений используется слово indirect, но учитывая его модель памяти возникает вопрос, разве компилятор сам не в состоянии разобраться с тем как нужно выделать память? По-видимому это ключевое слово введено скорее для человека.
Также мы можем видеть, что impl и extension в принципе выполняют примерно похожую работу, а типажи похожи на протоколы. Но в swift подход более компромиссный: не обязательно добавлять методы как расширения, их можно указать прямо в объявлении перечисления.

Теперь давайте просто посмотрим на пару примеров сопоставления с образцом:

rust (примеры взяты из rust by example)

match some_value {
    Ok(value) => println!("got a value: {}", value),
    Err(_) => println!("an error occurred"),
}

enum OptionalTuple {
    Value(i32, i32, i32),
    Missing,
}

let x = OptionalTuple::Value(5, -2, 3);

match x {
    OptionalTuple::Value(..) => println!("Got a tuple!"),
    OptionalTuple::Missing => println!("No such luck."),
}

let x = 1;

match x {
    1 ... 5 => println!("one through five"),
    _ => println!("anything"),
}

let x = 1;

match x {
    e @ 1 ... 5 => println!("got a range element {}", e),
    _ => println!("anything"),
}


Swift (код примеров взят из swiftbook)
let count = 3000000000000
let countedThings = "stars in the Milky Way"
var naturalCount: String
switch count {
case 0:
    naturalCount = "no"
case 1...3:
    naturalCount = "a few"
case 4...9:
    naturalCount = "several"
case 10...99:
    naturalCount = "tens of"
case 100...999:
    naturalCount = "hundreds of"
case 1000...999999:
    naturalCount = "thousands of"
default:
    naturalCount = "millions and millions of"
}
print("There are \(naturalCount) \(countedThings).")
//  выведет "There are millions and millions of stars in the Milky Way."

let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("(0, 0) is at the origin")
case (_, 0):
    print("(\(somePoint.0), 0) is on the x-axis")
case (0, _):
    print("(0, \(somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
    print("(\(somePoint.0), \(somePoint.1)) is inside the box")
default:
    print("(\(somePoint.0), \(somePoint.1)) is outside of the box")
}// выведет "(1, 1) is inside the box

let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
    print("on the x-axis with an x value of \(x)")
case (0, let y):
    print("on the y-axis with a y value of \(y)")
case let (x, y):
    print("somewhere else at (\(x), \(y))")
}
// выведет "on the x-axis with an x value of 2

Здесь в целом всё очень похоже. В сопоставлении не должно быть провалов, можно указывать диапазоны значений, есть возможность привязки значений, можно использовать кортежи в сопоставлении. Хотя Rust также позволяет использовать и целые структуры при сопоставлении. Можно задавать дополнительные условия через if и where. Но при этом в swift есть ещё дополнительные операторы управления потоком. Хотя я и не уверен, что их использование — это хорошая идея.

Более реальный пример

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

Для начала просто попробуем создать 3х мерный вектор и определить для него операции взятия индекса и сравнения:
rust

#[derive(Copy, Clone, PartialEq)]
struct Vec3 {
    x: i32,
    y: i32,
    z: i32
}

impl Index<usize> for Vec3
{
    type Output = i32;

    fn index<'a>(&'a self, i: usize) -> &'a Self::Output {
        match i {
            0   => &self.x,
            1   => &self.y,
            2   => &self.z,
            _   => panic!("Wrong index"),
        }
    }
}
impl IndexMut<usize> for Vec3
{
    fn index_mut<'a>(&'a mut self, i: usize) -> &'a mut Self::Output {
        match i {
            0   => &mut self.x,
            1   => &mut self.y,
            2   => &mut self.z,
            _   => panic!("Wrong index"),
        }
    }    
}


swift
struct Vector3 {
    var x: Int;
    var y: Int;
    var z: Int;

    subscript(i: Int) -> Int {
        get {
            precondition(i >= 0 && i < 3, "Index out-of-bounds")
            switch i {
            case 0: return self.x
            case 1: return self.y
            case 2: return self.z
            default: return 0
            }
        }
        set {
            precondition(i >= 0 && i < 3, "Index out-of-bounds")
            switch i {
            case 0: self.x = newValue
            case 1: self.y = newValue
            case 2: self.z = newValue
            default: break
            }
        }        
    }
}

func == (left: Vector3, right: Vector3) -> Bool {
    return (left.x == right.x) && (left.y == right.y) && (left.z == right.z)
}
func != (left: Vector3, right: Vector3) -> Bool {
    return !(left == right)
}


В rust'е на самом деле операторы реализуются через типажи, если структура реализует типаж Index, то для нее можно применять оператор [], в swift'е операторы взятия индекса записываются с помощью ключевого слова subscript и позволяют добавлять специальные предусловия. Rust же полагается на обычные ассерты. В случае же с другими операторами swift позволяет как переопределять существующие, так и определять свои собственные, причем с указанием приоритетов, ассоциативности. Всё это здорово может помочь при написании математических библиотек, сделав код более похожим на исходные математические выражения. Rust же умеет автоматически создавать реализации некоторых типажей через атрибут derive, что позволяет не писать код самому во многих тривиальных случаях.

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

rust

struct GenericPixmap<T> {
    w: usize,
    h: usize,
    
    data: Vec<T> 
}

impl<T> GenericPixmap<T> 
    where T: Copy + Clone 
{
    fn new(w: usize, h: usize, fill_value: T) -> GenericPixmap<T> {
        GenericPixmap {
            w: w,
            h: h,
            data: vec![fill_value; w*h]
        }
    }
}

impl<T> Index<usize> for  GenericPixmap<T> 
    where T: Copy + Clone 
{
    type Output = [T];

    fn index<'a>(&'a self, i: usize) -> &'a Self::Output {
        let from = i*self.w;
        let to = from+self.w;
        &self.data[from..to]
    }
}
impl<T> IndexMut<usize> for GenericPixmap<T> 
    where T: Copy + Clone 
{
    fn index_mut<'a>(&'a mut self, i: usize) -> &'a mut Self::Output {
        let from = i*self.w;
        let to = from+self.w;
        &mut self.data[from..to]
    }    
}

type Pixmap = GenericPixmap<u32>;

swift

struct GenericPixmap<T> {
    let w: Int
    let h: Int

    var data: [T]

    init(width: Int, height: Int, fillValue: T) {
        self.w = width
        self.h = height
        self.data = [T](count: w*h, repeatedValue: fillValue)
    }

    func indexIsValid(x: Int, _ y: Int) -> Bool {
        return x >= 0 && x < w && y >= 0 && y < h
    }

    subscript(x: Int, y: Int) -> T {
        get {
            precondition(indexIsValid(x, y), "Index out-of-bounds")
            return data[x * y + y]
        }
        set {
            precondition(indexIsValid(x,y), "Index out-of-bounds")
            data[x * y + y] = newValue
        }
    }
}

typealias Pixmap = GenericPixmap<UInt32>

Правила для обобщений в rust'е более строгие и нам нужно явно указывать, что шаблонный тип должен уметь копироваться и создавать свою копию. В swift'е же для полей структуры можно явно задавать их изменяемость. А ещё можно заметить ключевое слово init. Это конструктор класса или структуры, их может быть несколько, они могут друг другу делегировать свои полномочия. В результате это выливается в достаточно сложный и многоступенчатый процесс, который, тем не менее, точно гарантирует, что каждый член будет проинициализирован. В rust'е же есть почленная инициализация и соглашение на то, что объект должен создаваться статической функцией new. Если же процесс обещает быть сложным, то рекомендуется использовать фабрики. Что же касается статических функий, то синтаксис rust'а в этом смысле следует традициям python, а swift же C++.
Хочу заметить, что оператор индекса в swift может принимать любое количество аргументов любых типов, поэтому там можно написать оператор, получающий сразу конкретный элемент массива, в rust'е же нужно создавать срез.

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

trait Canvas
{
    fn set_pixel(&mut self, x: usize, y:usize, color:u32);
}

impl Canvas for Pixmap
{
    fn set_pixel(&mut self, x: usize, y:usize, color:u32)
    {
        self[x][y] = color;
    }
}


swift
protocol Canvas {
    func setPixel(x: Int, _ y: Int, color: UInt32);
}

class MyCanvas : Canvas {
    var pixmap: Pixmap
    
    init(width: Int, height: Int, fillValue: UInt32) {
        self.pixmap = Pixmap(width:width, height:height, fillValue:fillValue)
    }
    
    func setPixel(x: Int, _ y: Int, color: UInt32)
    {
        pixmap[x, y] = color
    }
}

К сожалению, не смог быстро разобраться с тем, как реализовать расширение для типа GenericPixmap, поэтому решил создать новый класс MyCanvas, который бы реализовывал протокол Canvas, в отличии от rust'а в swift'е можно наследоваться от протоколов и не только.

Теперь мы подошли к самому интересному — реализации алгоритма Брезенхема. Мы хотим нарисовать линию из точки (x1, y1, z1) в точку (x2, y2, z2), для этого нам нужно сделать (|x2-x1|, |y2-y1|, |z2-z1|) шагов в направлении, которое зависит от знаков выражения (x2-x1, y2-y1, z2-z1).
Итак, нам нужно пройти (rx, ry, rz) шагов в направлениях (sx, sy, sz), для этого мы находим ось, вдоль которой нужно совершить наибольшее число шагов. Перемещение на каждом шаге будет равно (rx/r[max, ry/r[max], rz/r[max]), при этом шаг будет происходить только если суммарное перемещение d стало больше единицы, тогда по оси делается шаг, а из суммарного перемещения вычитается единица. То есть:

d[i] += r[i] / r[max]
if d[i] >= 1 { r[i] -= s[i]; d[i] -= 1; }


Несложно заметить, что если домножить условие на rmax, то можно вообще обойтись без операции деления.
d[i] += r[i]
if d[i] >= r[max] { r[i] -= s[i]; d[i] -= r[max]; }


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

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

rust

struct RasterState {
    step: Vec3,
    d: Vec3,
    major_axis: usize,
}

struct LineRasterizer {
    from: Vec3,
    to: Vec3,

    state: Option<RasterState>
}

impl LineRasterizer {

    fn new(from: Vec3, to: Vec3) -> LineRasterizer {
        LineRasterizer {
            from: from,
            to: to,
            state: None
        }
    }

    fn next_point(&mut self) -> Option<Vec3> {
        match self.state {
            None => {
                let mut state = RasterState {
                    step: Vec3 { x: 0, y: 0, z: 0 },
                    d: Vec3 { x: 0, y: 0, z: 0 },
                    major_axis: 0
                };   

                let mut max = 0;
                for i in 0..3 {
                    let d = self.to[i] - self.from[i];
                    state.step[i] = if d > 0 { 1 } else { -1 };
                    
                    let d = d.abs();
                    if d > max {
                        max = d;
                        state.major_axis = i as usize;
                    };
                }

                self.state = Some(state);
                Some(self.from)
            },
            Some(ref mut state) => {
                if self.from == self.to {
                    None
                } else {
                    let from = self.from; let to = self.to;
                    let calc_residual_steps = |axis| { (to[axis] - from[axis]).abs() };
                    
                    self.from[state.major_axis] += state.step[state.major_axis];                    
                    let rs_base = calc_residual_steps(state.major_axis);
                    for i in 0..3 {
                        let rs = calc_residual_steps(i);
                        
                        if rs > 0 && i != state.major_axis {
                            state.d[i] += rs;
                            if state.d[i] >= rs_base {
                                state.d[i] -= rs_base;
                                self.from[i] += state.step[i];
                            }
                        }
                    }                    

                    Some(self.from)
                }
            },
        }
    }
}


swift
class LineRaster {

    class State {
        var step: Vector3
        var d: Vector3
        var majorAxis: Int

        init() {
            self.step = Vector3(x: 0, y: 0, z: 0)
            self.d = Vector3(x: 0, y: 0, z: 0)
            self.majorAxis = 0
        }
    }

    var from: Vector3
    let to: Vector3
    var state: State?

    init(from: Vector3, to: Vector3) {
        self.from = from
        self.to = to
    }

    func next_point() -> Vector3? {
        if let state = self.state {
            if (self.from == self.to) {
                return nil
            } else {
                let calsResidualSteps = {axis in return abs(self.to[axis] - self.from[axis])}
                
                self.from[state.majorAxis] += state.step[state.majorAxis];
                let rsBase = calsResidualSteps(state.majorAxis);
                for i in 0..<3 {
                    let rs = calsResidualSteps(i);
                    
                    if rs > 0 && i != state.majorAxis {
                        state.d[i] += rs;
                        if state.d[i] >= rsBase {
                            state.d[i] -= rsBase;
                            self.from[i] += state.step[i];
                        }
                    }
                }
                
                return self.from
            }
        } else {
            let state = State()
            var max = 0;
            for i in 0..<3 {
                let d = self.to[i] - self.from[i];
                state.step[i] = d > 0 ? 1 : -1;
                
                let da = abs(d);
                if da > max {
                    max = da;
                    state.majorAxis = i;
                };                
            }
            self.state = state
            return self.from
        }
    }
}

Состояние генератора я решил сделать в виде опционального значения, это позволяет нам легко и сразу вернуть исходную точку from из генератора без необходимости заведения дополнительных флагов. В rust опциональные значения сделаны просто через enum Option, в то время, как в swift'е они являются частью языка, что позволяет легко описывать опциональные цепочки вызовов без лишнего синтаксического шума.
В rust'е используется продвинутая система владения, чтобы подсказать ей, что мы одалживаем State из перечисления по ссылке, нужно писать ключевое слово ref. В swift'е же State по умолчанию ссылочный тип данных, а move семантики в языке пока не наблюдается, поэтому мы можем просто взять и распаковать state ни о чем не заботясь.

Писать код типа:

while let Some(point) = rasterizer.next_point() { ... }


Мне кажется не слишком изящным, гораздо логичнее для этого выглядит.
for point in generator { ... }


К счастью для того, чтобы можно было использовать цикл for достаточно просто реализовать несколько типажей для нашего генератора.
rust
impl Iterator for LineRasterizer
{
    type Item = Vec3;

    fn next(&mut self) -> Option<Self::Item> {
        self.next_point()
    }
}


swift
extension LineRaster : GeneratorType {
    func next() -> Vector3? {
        return self.next_point()
    }
}

extension LineRaster : SequenceType {
    func generate() -> LineRaster {
        return self
    }
}

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

Давайте немного померяемся производительностью

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

Наивный вариант сравнения:

rust

fn test_code(canvas: &mut Canvas) {
    let a = Vec3 { x: 0, y:0, z:0 };
    let b = Vec3 { x: 50, y: 55, z: -20 };

    let rasterizer = LineRasterizer::new(a, b);
    for point in rasterizer {
        let color = std::u32::MAX;
        canvas.set_pixel(point.x as usize, point.y as usize, color);
    }
}

for _ in 0..1000000 {
    test_code(&mut canvas)
}


swift
func testCode(canvas: Canvas) -> () {
    let a = Vector3(x: 0, y:0, z:0)
    let b = Vector3(x: 50, y:55, z:-20)
    let raster = LineRaster(from: a, to: b)
    for point in raster {
        let color = UInt32.max
        canvas.setPixel(point.x, point.y, color: color)
    }
}

...

var myCanvas: Canvas = canvas
for _ in 0..<1000000 {
    testCode(myCanvas)
}

Мы просто передадим ссылку на Canvas в тестируемую функцию и замеряем время.
Получилось 0.86 у rust против 5.3 у swift. Вполне вероятно, что rust как-то заинлайнил вызовы, а swift же остался на уровне динамической диспетчеризации. Чтобы проверить это попробуем написать обобщенную функцию.
rust

fn test_code_generic<T: Canvas>(canvas: &mut T) {
    let a = Vec3 { x: 0, y:0, z:0 };
    let b = Vec3 { x: 50, y: 55, z: -20 };

    let rasterizer = LineRasterizer::new(a, b);
    for point in rasterizer {
        let color = std::u32::MAX;
        canvas.set_pixel(point.x as usize, point.y as usize, color);
    }
}


swift
func testCodeGeneric<T:Canvas>(canvas: T) -> () {
    let a = Vector3(x: 0, y:0, z:0)
    let b = Vector3(x: 50, y:55, z:-20)
    let raster = LineRaster(from: a, to: b)
    for point in raster {
        let color = UInt32.max
        canvas.setPixel(point.x, point.y, color: color)
    }
}


Результаты 0.83 у rust, против 4.94 у swift, что говорит нам о том, что всё-таки swift сумел лучше соптимизировать код, но где-то ещё остались узкие места, в которых он не смог разобраться.

Теперь попробуем упаковать Canvas в box, а для swift воспользоваться модификатором inout, который по своему действию подобен &mut.
rust

fn test_code_boxed(canvas: &mut Box<Canvas>) {
    let a = Vec3 { x: 0, y:0, z:0 };
    let b = Vec3 { x: 50, y: 55, z: -20 };

    let rasterizer = LineRasterizer::new(a, b);
    for point in rasterizer {
        let color = std::u32::MAX;
        canvas.set_pixel(point.x as usize, point.y as usize, color);
    }
}


swift
func testCodeInout(inout canvas: Canvas) -> () {
    let a = Vector3(x: 0, y:0, z:0)
    let b = Vector3(x: 50, y:55, z:-20)
    let raster = LineRaster(from: a, to: b)
    for point in raster {
        let color = UInt32.max
        canvas.setPixel(point.x, point.y, color: color)
    }
}

Результаты 0.91 у rust, против 6.44 у swift.

Боксинг несколько замедлил выполнение кода, но не так уж и значительно, а вот добавление inout весьма существенно повлияло на swift. Видимо возможность поменять ссылку на canvas связывает оптимизатору руки.

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

В завершении хотелось бы сравнить, как rust и swift смотрятся на фоне старичка C++.

Вполне обыденный код бенчмарка
#include <iostream>
#include <vector>
#include <cassert>
#include <memory>
#include <cstdlib>

template <typename T>
struct GenericPixmap
{
    size_t w, h;
    std::vector<T> data;

    GenericPixmap(size_t w_, size_t h_, T fill_data = T()) :
        w(w_), h(h_),
        data(w_*h_, fill_data)
    {

    }

    T* operator[] (size_t i)
    {
        return &data[i*w];
    }
};

struct Vec3
{
    int x, y, z;

    int& operator[] (size_t i) 
    {
        assert(i >=0 && i < 3);
        switch (i) {
            case 0: return x;
            case 1: return y;
            case 2: return z;
            default: break;
        }
        return z;
    }
};

bool operator== (const Vec3 &a, const Vec3 &b)
{
    return a.x == b.x && a.y == b.y && a.z && b.z;
}
bool operator!= (const Vec3 &a, const Vec3 &b)
{
    return !(a == b);
}

struct RasterState
{
    Vec3 step;
    Vec3 d;
    size_t majorAxis;
};

struct LineRaster
{
    Vec3 from;
    Vec3 to;
    
    bool firstStep;
    RasterState state;

    LineRaster(const Vec3 &f, const Vec3 &t) : from(f), to(t), firstStep(true), state{}
    {}

    bool next_point()
    {
        if (firstStep) {
            size_t max = 0;
            for (int i = 0; i < 3; ++i) {
                auto d = to[i] - from[i];
                state.step[i] = d > 0 ? 1 : -1;

                d = std::abs(d);
                if (d > max) {
                    max = d;
                    state.majorAxis = i;
                }
            }
            firstStep = false;
            return true;
        } else {
            if (from == to)
                return false;
            else {
                auto calc_rs = [this](auto axis) { return std::abs(to[axis] - from[axis]); };

                from[state.majorAxis] += state.step[state.majorAxis];
                auto rs_base = calc_rs(state.majorAxis);
                for (int i = 0; i < 3; ++i) {
                    auto rs = calc_rs(i);
                    
                    if (rs > 0 && i != state.majorAxis) {
                        state.d[i] += rs;
                        if (state.d[i] >= rs_base) {
                            state.d[i] -= rs_base;
                            from[i] += state.step[i];
                        }
                    }
                }
                return true;
            }
        }
    }
};

using Pixmap = GenericPixmap<uint32_t>;

void test_code(Pixmap &pixmap)
{
    Vec3 a { 0, 0, 0 };
    Vec3 b { 50, 55, -20 };

    LineRaster raster(a, b);
    while (raster.next_point()) {
        const auto &p = raster.from;
        pixmap[p.x][p.y] = 0xFFFFFF;
    }
}

int main(int, char **) {
    Pixmap pixmap(300, 300, 0);

    Vec3 a { 0, 0, 0 };
    Vec3 b { 10, 5, -4 };

    LineRaster raster(a, b);
    while (raster.next_point()) {
        const auto &p = raster.from;

        uint32_t color = 0xffffff;
        pixmap[p.x][p.y] = color;
        std::cout << "C++: point x:" << p.x << " y:" << p.y << " z:" << p.z << " color:" << color << std::endl;
    }

    for (size_t i = 0; i < 1000000; ++i)
        test_code(pixmap);
}



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

0.79 у rust, против 0.31 у gcc.

Результат очень интересен: с одной стороны rust практически идентичную скорость с clang скорость показывает, но с другой стороны gcc просто превзошел всех. То есть в целом платформе llvm есть куда стремится, но в рамках неё rust уже в спину дышит clang'у, а это значит, что на нем уже можно вполне смело начинать писать критические по требованиям производительности участки.

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

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

Выводы

Разработчики swift'а заметно больше уделяют времени читабельности кода, добавляя множество синтаксического сахара в язык. Rust же более аскетичен в этом вопросе старается обойтись наименьшим количеством сущностей. Впрочем, многие вещи можно добавить при помощи макросов или плагинов к компилятору. За счет эффективных zero cost абстракций код на Rust'е получается более производительным, но зато и более сложным для восприятия. Swift же позволяет писать программы меньше обращая внимание на управление памятью, код при этом получается весьма и весьма эффективным, хотя и уступает по этому показателю rust'у.

Что касается инфраструктуры, то для swift'а есть прекрасная ide XCode, но доступная лишь для MacosX, а привязки с Cocoa позволяют уже сейчас писать и выпускать графические приложения для iOS или OsX. Rust же может похвастаться своим сargo и crates.io, где обитает и развивается огромное количество библиотек и приложений, но пока среди них нет ни развитой ide ни хорошего gui фреймворка.

К сожалению ничего нельзя сказать насчет многопоточного программирования потому, что в swift'е пока нет родной поддержки, только через grand central dispatch, но подход Rust'а позволяет писать одновременно быстрые и надежные приложения. Строгий компилятор просто убережет вас от ошибок синхронизации.

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

Послесловие

Бескомпромиссен и аскетичен rust, Светлой стороне благоприятен он, swift же сахаром синтаксическим своим на Темную сторону завести может.

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