При­шла в мою, за­ту­ма­нен­ную че­реп­но-мозго­вы­ми трав­ма­ми и ал­ко­голь­но-нар­ко­ти­че­ски­ми ве­ще­ства­ми, го­ло­ву идея сде­лать софт для по­лу­че­ния дан­ных син­хро­ни­за­ции брау­зе­ра Google Chrome, ко­то­рым поль­зу­ют­ся большинство трез­во­мыс­ля­щих лю­дей. Хро­ни­че­ские дол­бое­бы си­дят в 21м ве­ке на Opera, при­ду­мы­вая не­по­нят­ные ар­гу­мен­ты вро­де "она крас­нень­кая и с кра­си­вым старто­вым экра­ном". IE - вы­бор тех, ко­му все рав­но, к 10й вер­сии в май­кро­соф­те его от­лич­но приче­са­ли, но по­про­буй те­перь пере­бо­ри этот стерео­тип ше­стерки. Firefox - для но­сталь­ги­ру­ю­щих по сот­ням ты­сяч пла­ги­нов, для ани­меш­ни­ков, не­на­ви­дя­щих им­пе­ри­а­лизм и дик­та­ту­ру Google, или же стра­да­ю­щих на­вяз­чи­вой идеей превра­ще­ния брау­зе­ра в по­что­вый кли­ент, чат, хле­бо­печ­ку и циф­ро­вое вла­га­ли­ще.

UPD: Synchromium те­перь до­сту­пен для ва­ших ай­фон­чи­ков в iTunes. Жал­ко 99 цен­тов? Пи­ши мне - вы­шлю про­мо­код.

UPD: Не­об­хо­ди­мую сум­му я за­ра­бо­тал, син­хро­ми­ум до­сту­пен for free для всех же­ла­ю­щих. Тем бо­лее, с вы­хо­дом Google Chrome на iOS ак­ту­аль­ность про­грам­мы утра­ти­лась.



UPD: Synchromium на my-chome.ru и в их же vk
 
Про­грам­ма, ра­зу­ме­ет­ся, для теле­фо­нов мар­ки iPhone, ко­то­рые ис­поль­зу­ют не толь­ко трез­во­мыс­ля­щие и идей­ные, но все ко­му не лень, со­зда­вая огром­ный по­тре­би­тель­ский ры­нок, го­то­вый прогло­тить лю­бое при­вле­ка­тель­ное предло­же­ние. Я сей­час про нор­маль­ные стра­ны, если что. Де­лал я эту не­большую ути­литу под на­зва­ни­ем Synchromium (ну чотко же, да? тут вам в на­зва­нии и син­хро­ни­за­ция, и хро­ми­ум) не столь­ко для алч­но­го же­ла­ния на­жи­вы (по­сле review обе­зьян­ка­ми из эп­п­ла она по­явит­ся в AppStore по смеш­ной це­не в 99 цен­тов, о чем я со­об­щу в уют­нень­ком лас­ко­вом твит­тере, там же бу­дет про­ве­де­на раз­да­ча 40 про­мо­ко­дов для бес­плат­ной за­груз­ки), сколь­ко для озна­ком­ле­ния с совре­мен­ным инстру­мен­та­ри­ем раз­ра­ботки под iOS. По­след­ний раз я за­ни­мал­ся этим в те да­ле­кие вре­ме­на, когда XCode был еще в тре­тьей вер­сии, iOS на­зы­ва­лась iPhoneOS, а тол­пы ин­ду­со-ки­тай­цев еще не ку­пи­ли се­бе БУш­ный ма­ки и не на­ча­ли дем­пин­го­вать це­ны своим ущерб­ным предло­же­ни­ем.
 
Обыч­но я раз­би­рал­ся в спосо­бе пере­да­чи дан­ных сле­ду­ю­щим об­разом: запус­кал mitmproxy (не­за­ме­ни­мая вещь, ко­то­рая по­сле уста­нов­ки сво­е­го кор­не­во­го сер­ти­фи­ка­та на це­ле­вую ма­ши­ну, ре­а­ли­зу­ет ssl interception ори­ги­наль­ным об­разом - realtime ге­не­ра­ци­ей своих само­под­писных, но ва­лид­ных сер­ти­фи­ка­тов для всех https запро­сов), на­стра­и­вал прок­си сер­вер в вир­ту­аль­ной ма­ши­не с Windows, запус­кал ис­пы­ту­е­мую про­грам­му и на­блю­дал за ее по­ве­де­ни­ем. К сча­стью, в совре­мен­ном ми­ре для большинства сер­ви­сов REST API в со­че­та­нии с JSON де-фа­кто стал стан­дартом об­ме­на ин­фор­ма­ци­ей меж­ду кли­ен­том и сер­ве­ром. Но я за­был, что это же Google, йоп­та! Paranoid Android, как пе­ли ра­дио­го­ло­вые. Слож­но опи­сать, на­сколь­ко силь­ным бы­ло мое разо­ча­ро­ва­ние, когда в при­выч­ном Answer body вме­сто ожи­да­е­мой ка­ши из XML те­гов или де­сят­ков ско­бок JSON я уви­дел би­нар­ный не­рас­пар­си­ва­е­мый яд с вкрап­ле­ни­ем ку­чи base64 encoded строк и unicode ме­ша­ни­ны. Это так же обид­но, как и вы­играть в на­ци­о­наль­ной ло­терее поезд­ку на 5 дней в Крым, в част­ный сек­тор, за 7 ки­ло­мет­ров от мо­ря.
 
На этом мож­но бы­ло мах­нуть на идею ру­кой, взять би­лет в Банг­кок и пой­ти в гоу-гоу бар смот­реть на быв­ше­го му­жи­ка в ко­ро­тень­кой юб­ке и в то­пи­ке, как бы стран­но это не зву­ча­ло. К сча­стью, Google Chrome это Google Chromium на сте­рои­дах, ко­то­рый как бы опен­сор­се со все­ми вы­те­каю­щи­ми. Тут нуж­но от­ме­тить ве­ли­ко­леп­ное ка­че­ство и ло­ги­ку ор­га­ни­за­ции ко­да в ре­по­зи­та­рии. Им за­чи­ты­ва­ешь­ся как Ви­ки­пе­ди­ей: на­чи­на­ешь изу­чать код син­хро­ни­за­ции дан­ных, а че­рез ка­кое-то вре­мя уже смот­ришь на их враппер во­круг OpenSSL или на уро­вень аб­страк­ции NaCl. Но что-то я запиз­дел­ся. Вкрат­це, в Google изоб­ре­ли свой про­то­кол об­ме­на под на­зва­ни­ем Protocol Buffers. Тер­мин "про­то­кол", прав­да, тут не под­хо­дит, ско­рее способ ин­кап­су­ля­ции дан­ных, так как про­то­бу­фер в на­шем слу­чае ра­бо­та­ет по­верх HTTP. 
 
Са­ма ре­а­ли­за­ция на­по­мнила мне та­кой се­бе ORM для API. В proto файле вы опи­сы­ва­е­те ин­тер­фейс бу­ду­ще­го объек­та, а по­том спе­ци­аль­ный транс­ля­тор-компи­ля­тор protoc пере­во­дит эти файлы из род­но­го фор­ма­та и син­так­си­са в клас­сы на C++, Python, Java пря­мо "ис­ка­роп­ки", ли­бо в лю­бой дру­гой, под­дер­жи­ва­е­мый нео­фи­ци­аль­но. Про­то­син­так­сис до­ста­точ­но бо­гат, ре­а­ли­зо­ва­но на­сле­до­ва­ние, ин­кап­су­ля­ция, по­ли­мор­физм и про­чие пре­ле­сти OOD. Идея не но­ва (тот же Apache Thrift), но охуен­на в сво­ем удоб­стве и про­сто­те. По су­ти, опи­сав весь про­то­кол вза­и­мо­дей­ствия один раз, вы по­лу­ча­е­те го­то­вый cross-language код ready for work. А еще этот способ при­зван обез­опа­сить ва­ше API от по­сто­ронних глаз и пытли­вых умов, без proto файла прак­ти­че­ски не­ре­аль­но сфор­миро­вать из­ме­нен­ный запрос или разобрать от­вет во что-то чи­та­е­мое. Имен­но прак­ти­че­ски, ведь на тео­ре­ти­че­ском уров­не ги­по­те­ти­че­ская обе­зья­на, уда­ряя паль­ца­ми по кла­виа­ту­ре пе­чат­ной ма­шин­ки слу­чай­ным об­разом, ра­но или позд­но на­пе­ча­та­ет од­ну из пьес Шекс­пи­ра. Но где вы ви­де­ли та­ких обе­зьян? Совре­мен­ные пи­са­те­ли из РФ и Укра­и­ны не в счет. Protocol Buffers - основ­ная тех­ни­че­ская при­чи­на, по­че­му Google+ все еще не име­ет сто­ронней ре­а­ли­за­ции функ­ции от­прав­ки со­об­ще­ния. Мо­биль­ный кли­ент для об­ме­на дан­ны­ми ис­поль­зу­ет имен­но его.
 
К мо­е­му сча­стью, для ObjectiveC су­ще­ство­ва­ла unofficial ре­а­ли­за­ция про­то­ко­ла срав­ни­тель­но до­стой­но­го ка­че­ства. Все дескрип­то­ры бы­ли взя­ты из ис­ход­но­го ко­да Google Chromium и успеш­но со­бра­лись в .m и .h non-ARC файлы. Собра­лись, прав­да, не без проблем, ре­зультат при­шлось немного ре­дак­ти­ро­вать вруч­ную для устра­не­ния не­ко­то­рых кри­ти­че­ских оши­бок, ко­то­рые сво­ди­лись, как пра­ви­ло, к не­по­нят­но­му пере­на­зна­че­нию пере­мен­ных и на­сла­и­ва­ю­щим­ся об­ла­стям ви­ди­мо­сти. Ну а дальше де­ло тех­ни­ки. Во многом мне по­мо­гла вну­трен­няя стра­ни­ца Google Chrome до­ступ­ная по ад­ре­су chrome://sync. Мно­го пи­сать о самой ре­а­ли­за­ции я не бу­ду, чуть ни­же вас ждет це­лый аб­зац о мо­их впе­чат­ле­ни­ях от XCode, iOS и еще один аб­зац с опи­са­ни­ем пробле­мы, ко­то­рую я впер­вые по­про­бую ре­шить с по­мо­щью чи­та­телей это­го бло­жи­ка. 
 
С само­го на­ча­ла для хра­не­ния дан­ных я ре­шил по­про­бо­вать мод­ный CoreData - уро­вень аб­страк­ции от син­так­си­са sqlite (не толь­ко, но в большинстве сво­ем). Ска­жу сра­зу, я ни ра­зу не ObjectiveC про­грам­мист. Вы­учить син­так­сис по­сле всех тех язы­ков, ко­то­рые я знаю, пробле­мы не со­ста­ви­ло, но я ни­когда не смо­гу по­нять эту увле­чен­ность объек­та­ми и по­ро­жде­ние не­нуж­ных сущ­но­стей. Язык раз­ви­ва­ет­ся, пы­та­ясь из­ба­вить­ся от тя­же­ло­го на­сле­дия си с по­мо­щью бло­ков, но­ти­фи­кей­ш­нов или но­во­го син­так­си­са конструк­то­ров для основ­ных ти­пов, но все еще вы­гля­дит крайне монстру­оз­но с этой ка­шей де­ле­га­тов и обо­ра­чи­ва­ния в клас­сы всего то­го, что от­лич­но по­ме­ща­лось и в при­ми­ти­вы. Так вот по­лу­чи­лось и с CoreData. За при­зрач­ным удоб­ством раз­мет­ки схе­мы ба­зы дан­ных скры­ва­ет­ся ка­ша из коор­ди­на­то­ров, контек­стов, фет­чед-рекве­стов и про­чих из­ли­шеств. Да, при пра­виль­ном под­хо­де все это офи­ген­но ин­те­гри­ру­ет­ся в ин­фра­струк­ту­ру то­го же UITableView, но я, ви­ди­мо, про­сто не до­стиг дзе­на. И имен­но поэто­му ис­поль­зо­вал FMDB - оберт­ку над сиш­ной sqlite3 биб­лио­те­кой с raw sql request syntax. Все очень про­сто и со вку­сом, а как лег­ко ин­те­гри­ро­ва­лось в тот же TableView? Ну про­сто пес­ня.
 
Сам XCode из­ме­нил­ся. Я бы не ска­зал, что в луч­шую сто­ро­ну, раз­ви­тие дви­жет­ся в направ­ле­нии, ко­то­рое нам пы­та­ют­ся пре­под­не­сти еди­но­вер­ным. Бе­зу­слов­но, ARC по­ра­до­вал про­жжен­ную release-ами и dealloc-ами ду­шу ста­ро­го вол­ка. Впро­чем, воз­ник­ли опре­де­лен­ные труд­но­сти в по­ни­ма­нии прин­ци­пов его ра­бо­ты. У ме­ня был UIViewController c всу­ну­тым в не­го UIWebView, вся эта кра­со­та по­ка­зы­ва­лась поль­зо­ва­те­лю че­рез pushViewController сиг­нал UINavigationController-а. И вот, если вдруг поль­зо­ва­тель откры­вал этот экран с ве­бвью и за­гру­жал в ней ка­кой-то ад­рес, а по­том, не до­ждав­шись окон­ча­ния за­груз­ки, на­жи­мал Back, то весь UIViewController де­ал­ло­кал­ся, но при этом, по окон­ча­нию за­груз­ки, ве­бвью, ко­то­рый вро­де как бы дол­жен был уме­реть вме­сте с ро­ди­телем, посы­лал уже мерт­во­му па­па­ше сиг­нал "па­цан­трэ, праздну­ем, я за­кон­чил гру­зить", что, есте­ствен­но, при­во­ди­ло к па­де­нию всего в ERR_BAD_ACCESS. Это ре­ши­лось от­сыл­кой стоп-сиг­на­ла ве­бвью из viewWillDisappear, но ка­жет­ся ка­ким-то пе­чаль­ным недо­ра­зу­ме­ни­ем. Ве­ро­ят­ней всего, име­ла ме­сто моя ту­пость, ко­то­рая поз­во­ли­ла сде­лать де­ле­га­том UIWebView объект, ко­то­рый этот ве­бвью и со­здал, но тем не ме­нее. 
 
Пол­ным фа­ка­пом я счи­таю ис­то­рию со Storyboard. За­чем это бы­ло сде­ла­но оста­ет­ся для ме­ня за­гад­кой. Воз­мож­но, ре­дак­ти­ро­ва­ние ин­тер­фей­са в XIB недо­ста­точ­но тор­мо­зи­ло. С од­ной сто­ро­ны все ста­ло как-то удоб­ней и ло­гич­ней (осо­бен­но в све­те Static Cells, те же экра­ны на­строек или фор­мы вхо­да мож­но те­перь де­лать не всле­пую в ко­де, а пря­мо в UI ди­зайне­ре), но с дру­гой сто­ро­ны я не ви­жу смыс­ла в отоб­ра­же­нии всех экра­нов при­ло­же­ния, со­еди­нен­ных segue стрел­ка­ми. Ну лад­но, все это име­ло бы пра­во на жизнь, если бы не жут­чайшие ла­ги. По­сле 20 разных контрол­ле­ров в од­ном Storyboard на­чи­на­ет­ся по­кад­ро­вый цирк, а 8GB опе­ра­тив­ной па­мя­ти ка­те­го­ри­че­ски не хва­та­ет. И да, в 21 ве­ке XCode все еще не мо­жет из­ба­вить­ся от дет­ской бо­лез­ни неаде­кват­ной ор­га­ни­за­ции проек­та. Все про­сто ва­лит­ся ско­пом в од­ну пап­ку, ваш по­кор­ный слу­га по­тра­тил це­лый час, при­во­дя код в нор­маль­ный чи­та­е­мый вид не толь­ко в IDE, но и в Finder-Explorer. 
 
Раз уж вы до­чи­та­ли до само­го кон­ца, то самое вре­мя по­го­во­рить о чем-то ин­терес­ном. Пре­жде всего, фор­к­нуть или ска­чать код Synchromium вы мо­же­те на GitHub. Я счи­таю, что большинство при­ло­же­ний для iOS долж­ны быть opensource. Это не ли­ша­ет раз­ра­бот­чи­ков ка­ких-то ги­пер­при­бы­лей, ведь для компи­ля­ции ска­чан­ных ис­ход­ни­ков не под iOS Simulator или хо­тя бы уста­нов­ки на свой де­вайс нуж­но по­ку­пать Developer License за 99 аме­ри­канских ма­рок. А на jailbreak-ну­тый де­вай­сах ва­ша про­грам­ма и так ра­но или позд­но по­явит­ся в Installous-e.
 
Впер­вые за всю ис­то­рию су­ще­ство­ва­ния это­го бло­га, я про­шу по­мо­щи и обе­щаю за нее де­неж­ное воз­на­гра­жде­ние. Не­большое, но вы бу­де­те гор­ды со­бой, про­слав­ле­ны в этом бло­жи­ке и в самой про­грам­ме. Че­го уж там, я да­же сде­лаю та­ту­и­ров­ку с ва­шей фото­гра­фи­ей. Пробле­ма со­сто­ит в сле­ду­ю­щем - в Google Chrome су­ще­ству­ет воз­мож­ность cшиф­ро­вать свои дан­ные син­хро­ни­за­ции. Со­труд­ни­ка­ми Google был раз­ра­бо­тан соб­ствен­ной ал­го­ритм (вас это еще удив­ляет?) под ко­до­вым на­зва­ни­ем Nigori. По су­ти, это да­же не ал­го­ритм шиф­ро­ва­ния, а кон­цеп­ция об­ме­на дан­ны­ми меж­ду об­ла­ком и устрой­ством. По­дроб­нее о нем мож­но про­чи­тать. И вот пере­до мной вста­ла за­да­ча: как расшиф­ро­вы­вать за­шиф­ро­ван­ные дан­ные. Что мо­жет быть про­ще, когда под ру­кой есть от­лич­ные ис­ход­ни­ки Хро­ми­у­ма? Да, при­знаю, я бес­про­буд­но туп. Да­же имея весь ма­те­ри­ал на ру­ках, я не мо­гу пере­не­сти его на плат­фор­му iOS и CommonCrypto фрейм­ворк. Чуть ни­же сле­ду­ет мое ви­де­нье ал­го­рит­ма с ссыл­ка­ми на ис­ход­ный код.
 
Нас ин­тере­сует файл nigori.cc. Пре­жде всего, нуж­но обра­тить вни­ма­ние на NigoriStream-класс, ко­то­рый ре­а­ли­зу­ет свое­об­разный concat строк че­рез <<. Но мы не в C++, да и мно­же­ствен­ной за­пи­си в по­ток нам не нуж­но, до­ста­точ­но про­сто объеди­нить две стро­ки вро­де concatNigori:@"localhost" withString:@"dummy". Да­лее пере­хо­дим к фор­миро­ва­нию клю­чей по­мо­щью ал­го­рит­ма PBKDF2. Для на­ча­ла нам нуж­но сфор­миро­вать sUser ключ. Он ге­не­ри­ру­ет­ся с HMAC SHA1 хе­ши­ро­ва­ни­ем на­шей кон­кат стро­ки на ме­сте псев­до­ран­дом­ной функ­ции, па­ро­лем шиф­ро­ва­ния, и дру­ги­ми па­ра­мет­ра­ми из nigori.h (длин­на клю­ча 128 бит и ко­ли­че­ство итера­ций рав­ное 1001). Тут воз­ни­кает пер­вая слож­ность, на­ша кон­кат стро­ка со­зда­ет­ся сле­ду­ю­щим об­разом: NigoriStream salt_password << username << hostname. А вот со зна­че­ни­я­ми пере­мен­ных username и hostname не­большая пробле­ма. Нас­коль­ко я по­нял, в Google ре­ши­ли сде­лать их ста­тич­ны­ми localhost и dummy, упо­ми­на­ния об этом я на­шел в ко­де юнит­те­стов (что не по­ка­за­тель­но) и тут. По­лу­чен­ный salt (ко­то­рый мы пере­во­дим в стро­ко­вой фор­мат с по­мо­щью GetRawKey ме­то­да) мы бу­дем ис­поль­зо­вать при ге­не­ра­ции сле­ду­ю­щих клю­чей kUser (128 бит, 1002 цик­ла итера­ции), kEnc (128 бит, 1003 цик­ла итера­ции), kMac (128 бит, 1004 цик­ла итера­ции). В ка­че­стве псев­до­слу­чай­ной функ­ции тут ис­поль­зу­ет­ся, на­сколь­ко я по­нял, AES128. Те­перь пере­хо­дим не­по­сред­ствен­но к на­шей за­шиф­ро­ван­ной стро­ке. Пре­жде всего, нам нуж­но де­ко­ди­ро­вать ее из base64 в стро­ку. Тут у ме­ня воз­ни­кает пробле­ма #2, мы не зна­ем ко­ди­ров­ки. По­до­зре­ваю, что это ко­неч­но же не ASCII, ме­то­дом пере­бо­ра (StringUsingEncoding для NSData) по­лу­чи­лось уста­но­вить, что стро­ка де­ко­ди­ру­ет­ся лишь в UTF16 (я без по­ня­тия, нуж­но вы­би­рать big или little endian). Да­лее на­резаем по­лу­чен­ную стро­ку на кус­ки. Пер­вые 16 байт - IV, по­след­ние 32 байта - hash, все, что меж­ду ни­ми - на­ша за­шиф­ро­ван­ная стро­ка. До расшиф­ров­ки нам еще да­ле­ко, сна­ча­ла нуж­но убе­дить­ся в том, что стро­ка бы­ла за­шиф­ро­ва­на имен­но на­шим па­ро­лем. Срав­ни­ва­ем на­ши по­след­ние 32 байта в ви­де hash стро­ки с HMAC SHA256 хеш­су­мой на­ше­го kMac. В слу­чае сов­па­де­ния ини­ци­а­ли­зи­ру­ем AES CBC де­крип­тор клю­чем kEnc и те­ми пер­вы­ми 16 байта­ми IV. Этим де­крип­то­ром расшиф­ро­вы­ва­ем стро­ку и все.
 
Пиз­дец, да. Не­по­нят­но, за­чем та­кая па­ра­нойя, когда на ко­неч­ной ма­ши­не все па­ро­ли хра­нят­ся в sqlite ба­зе дан­ных без шиф­ро­ва­ния во­об­ще. Я пы­тал­ся со­брать nigori.cc в static lib, но по­тер­пел неу­да­чу - необ­хо­ди­мые за­ви­си­мо­сти про­сто от­сут­ство­ва­ли. Воз­мож­но, есть ве­ро­ят­ность со­брать OpenSSL, а не NSS вер­сию, под iOS это долж­но ра­бо­тать, но у ме­ня про­сто не хва­та­ет ума. Же­ла­ю­щие по­мочь при­гла­ша­ют­ся в скайп orl-light или в по­чту vladimir@smirnov.im, где я с удо­воль­стви­ем расска­жу вам все де­та­ли. Та­кой день.