
1. Что это такое.
Это режим работы binkp, на 100% защищенный от повторной передачи файлов
(и, естественно, от потерь) независимо от того, в какой момент оборвалось
соединение.
Это достигается за счет некоторого замедления протокола (информация о
следующем файле передается только после получения M_GOT на предыдущий),
задержки перед переименованием файла в его настоящее имя (это происходит
только после получения M_FILE на следующий файл, т.е. когда мы уверены,
что на той стороне файл прибит), и сохранения статуса сессий в файлах
*.stc.

2. Как оно работает.
Протокол binkp оказался очень хорошо адаптированным для введения этой
фичи. Не пришлось его менять вообще, меняется только поведение сторон.
Основные тезисы:
- сессия в ND-mode включается только если обе стороны на нее согласны.
  То есть если обе стороны сказали OPT ND при handshake. Отвечающая сторона
  всегда говорит OPT ND, если она поддерживает этот режим. Таким образом,
  не нужно менять (и даже проверять) версию протокола.
- опция ND включает в себя опцию NR. То есть ND без NR не бывает.
  Вызывающая сторона при handshake говорит "OPT NR ND", отвечающая -
  просто "OPT ND".
- начало передачи следующего файла (offset request) происходит только
  после приема M_GOT или M_SKIP на предыдущий.
- файл на приеме переименовывается только после начала передачи следующего,
  когда мы точно знаем, что там получили M_GOT и удалили файл. Введено
  новое поле state.in_complete - полностью принятый файл, который ждет
  переименования.
- на передающей стороне между удалением файла и получением M_GET на
  следующий в файле *.stc (status-file) сохраняются параметры
  предыдущего файла - мы не знаем, переименовался ли он на принимающей
  стороне, и при обрыве сессии в следующий раз мы его предложим (но
  передавать не будем - нечего, предложим только для переименования).
- статус физически сохраняется на диск каждый раз, чтобы не было потерь
  не только в случае обрыва соединения, но и при сбое питания, скажем.
- передача файлов на узел всегда начинается с передачи статуса, независимо
  от того, включена ли ND- или NR-mode. Иначе файл может потеряться (не
  переименоваться) при выключении ND-mode.

3. Некоторые подробности реализации.
Все было бы совсем просто, если бы у remote было всего одно AKA. Тогда
status можно было бы передавать всегда в начале сессии, и забыть о нем.
В случае нескольких AKA пришлось обрабатывать status-файлы на этапе
сканирования outbound (модуль ftnq); появились новые поля state'а:
cur_addr (адрес, для которого в данный момент передается почта) и
ND_addr (адрес, для которого мы будем обновлять status - он несколько
запаздывает по отношению к cur_addr).

Если remote согласился принимать файл, указанный в status (т.е. он его
уже переименовал и считает этот новым), то мы не можем просто так
отказаться его передавать и перейти к следующему файлу - на remote не
совпадет GET_FILE_balance. Поэтому нам надо всегда на M_GET отвечать
"M_FILE name offset time 0", и только потом переходить к новому файлу
(remote скажет "file transfer interrupted", и все будет нормально).

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

Было большое искушение не удалять файл на передающей стороне до его
переименования на принимающей, а в статусе сохранять информацию о том,
что этот файл передавать не нужно (только предложить). В этом случае
при удалении статуса или невозможности его сохранить может возникнуть
дуп (как на обычном -NR), но не будет потери файла. Но от этого
пришлось отказаться: если файл лежит в outbound, туда может быть
что-то допаковано, и его прийдется передавать опять, т.е. возникнут
те же дупы. Поэтому при невозможности сохранения статуса приходится
разрывать сессию, а при его удалении ручками файл может не быть
переименован принимающей стороной. Что ж, при удалении самого файла
тоже возможна его потеря - файл *.stc ничем не хуже. ;-)

Можно было бы сделать режим защиты от дупов и без замедления
протокола (при -NR), за счет того, что перед передачей файла идет
две посылки (запрос смещения и собственно начало передачи):
- при получении M_GOT мы выставляем status и удаляем файл;
- при получении "M_FILE <name> <size> 0" мы переименовываем файл,
  поскольку знаем, что remote получил ответ на offset request, а
  значит, и M_GOT от предыдущего файла;
- при получении M_GOT мы можем удалить предыдущий статус, потому
  что знаем, что принят следующий файл, а значит, было принято сообщение
  M_FILE о нем, т.е. предыдущий был переименован.
Недостаток этого метода заключается в том, что status всегда непустой,
т.е. следующая сессия всегда будет начинаться с передачи fake-файла,
даже если предыдущая сессия была завершена успешно (иначе нужно делать
еще один цикл подтверждений в конце сессии, т.е. изменение самого binkp)
 В общем, я от этого способа решил отказаться. Пусть лучше медленнее,
но без непонятных юзеру действий binkd и без толпы файлов .stc в
outbound (в том, что реализовано, файл .stc появляется достаточно редко,
только при обрыве сессии между получением M_GOT на один файл и M_GET на
следующий).

4. Недостатки.
- замедление работы протокола в режиме ND. Протокол в режиме ND
  работает примерно настолько же медленнее режима NR, насколько
  режим NR медленнее нормального режима;
- отъедается чуть больше памяти (увеличился размер структуры state);
- при ответе на входящее соединение всегда передается "OPT ND",
  показывающая, что мы поддерживаем этот режим - она может удивлять
  (а может и рекламировать), а кто-то скажет, что это избыточный
  траффик ;-)
- очистка статуса последнего переданного файла происходит только
  непосредственно перед окончанием сессии, т.е. после полной передачи
  всех файлов в _обе_ стороны.

5. Грабли (выявлены в процессе тестирования):
- файл передался, от него есть статус, на принимающей стороне ждет
переименования, произошел обрыв, и при следующей сессии оказалась
bsy на главное aka передающей стороны. Тогда сессия проводится с
вторым AKA (но та сторона об этом не знает), принимается статус,
расценивается как другой файл (from-address ведь уже не тот),
подтверждаем прием, передающая сторона думает, что раз хотят
принимать с начала, значит, уже переименовали, и со спокойной
совестью статус удаляет. Решение: при приеме файла проверять все
предъявленные aka, в т.ч. занятые.
- возможна ситуация, когда файл есть только в "недокаченном" (а
на самом деле, непереименованном) виде, когда переименование
планируется при передаче статуса. Если же другой линк предложит
файл с тем же именем, то при kill-dup-partial-files "недокаченный"
файл будет прибит. Решение: при kill-dup-partial-files прибивать
только файлы с тем же именем от того же линка, а не все файлы с
таким именем.
- опция ND не включает в себя NR, ведь при ответе всем говорится
ND, и если это включает в себя NR, то все сессии будут проводиться
в режимах ND или NR, независимо от желания "звонящего". То есть,
для корректной работы, опция ND может предъявляться только при
binkp 1.1 или выше, и звонящий может предъявлять ND только вместе
с NR, но не саму по себе. Кстати, если при ответе говорить "OPT NR",
то можно всем звонящим принудительно навязывать NR-mode (если они
это умеют, конечно) - интересная фича. ;-)
- при согласии принимать файл с начала нельзя предлагать сразу
следующий файл из-за внутренних счетчиков binkd. Нужно сначала
сообщить, что мы будем передавать файл с начала, и только после
этого переходить к следующему файлу, тогда это воспринимается
нормально.

6. Пример сессии в режиме ND (рассмотрена только передача в одну сторону):

    master                               slave

#if status=="X size time"
>> M_FILE X <size> <time> -1
                                   << M_FILE X <size> <time> -1
                                   >> M_GET X <size> <time> <size>
>> M_FILE X <size> <time> <size>
                                   << M_FILE X <size> <time> <size>
                                   >> M_GOT X <size> <time>
<< M_GOT X <size> <time>
#endif
>> M_FILE A <size> <time> -1
                                   << M_FILE A <size> <time> -1
                                   <rename X if exists>
                                   >> M_GET A <size> <time> <offset>
<< M_GET A <size> <time> <offset>
<set status to "">
>> M_FILE A <size> <time> <offset>
>> DATA
                                   << M_FILE A <size> <time> <offset>
                                   << DATA
                                   >> M_GOT A <size> <time>
<< M_GOT A <size> <time>
<set status to "A size time">
<remove A>
>> M_FILE B <size> <time> -1
                                   << M_FILE B <size> <time> -1
                                   <rename A>
                                   >> M_GET B <size> <time> <offset>
<< M_GET B <size> <time> <offset>
<set status to "">
>> M_FILE B <size> <time> <offset>
>> DATA
                                   << M_FILE B <size> <time> <offset>
                                   << DATA
                                   >> M_GOT B <size> <time>
<< M_GOT B <size> <time>
<set status to "B size time">
<remove B>
>> EOB
                                   << EOB
                                   <rename B>
...
>> EOB                             >> EOB
<< EOB                             << EOB
<set status to "">
<hangup>                           <hangup>


7. Приложение. ;-)

─ RU.BINKD (2:463/68) ─────────────────────────────────────────────── RU.BINKD ─
 Msg  : 46 of 46                            Uns Loc                             
 From : Pavel Gulchouck                     2:463/68        Втp 05 Май 98 13:49 
 To   : Oleg Zrozhevsky                                                         
 Subj : Re: заколебало yже                                                      
────────────────────────────────────────────────────────────────────────────────
Hi Oleg!

Wed Apr 08 1998, Oleg Zrozhevsky ==> Pavel Gulchouck:

 OZ> Тема, ставшая пpоклятием BinkD...

 IB>> А то y многих binkd ассоцииpyется с yвеличением дyпов

 PG>> Тyт главное - не тоpопиться. Чтобы не сделать еще что-то некpасивое,
 PG>> непpавильное, и пpоблемy не снимающее, а лишь смягчающее.

 PG>> Я yтвеpждаю, что возможен пpотокол, пpи котоpом pазpыв связи в
 PG>> пpоизвольный момент вpемени не пpиведет ни к потеpе файла (напpимеp,
 PG>> так и не пеpеименовали), ни к его повтоpномy пpиемy.
 PG>> Пpинимаются пpедложения. Я пока воздеpжyсь (поизyчаю binkp). Как
 PG>> появится вpемя, могy и pеализовать что-нибyдь.

 OZ> Можно я начнy?

[...]
 OZ> Я пpедлагаю боpоться с вышеописанной пpобеммой следyющим обpазом.
 OZ> 1. Каждая стоpона по окончании BINKP-сессии сохpаняет статyс пpошедшей с
 OZ> пpотивоположным yзлом сессии.

Да, без сохpанения некого статyса сессии боpоться не полyчится. Вопpос только в 
том, чтобы сделать этот статyс минимальным, не делать большого overhead, и
минимизиpовать изменения в binkp.

 OZ> В этот статyс входят: а) флаг yспешно завеpшенной пеpедачи (true, если для
 OZ> всех пеpеданных файлов полyчены подтвеpждения пpиема),

Угy.

 OZ> б) список пеpеданных файлов с их идентификационной инфоpмацией;

Ой.

 OZ> в) список пpинятых файлов с идентификационной инфоpмацией.

Ойй... :-(

 OZ> 2. Пpи начале следyющей сессии пеpедатчик пpовеpяет статyс пpошлой сессии.
 OZ> Если пеpедача во вpемя пpошлой сессии завеpшилась yспешно (флаг true), то
 OZ> дальше пеpедача пpодолжается по обычномy алгоpитмy.

Угy.

 OZ> Если же нет, то п.3
[...]
 OZ> Вот вкpатце и все. Hекотоpые детали я наpочно опyстил, чтобы не
 OZ> загpомождать нюансами общей сyти.

 OZ> Ждy ваших отзывов.

Очень много инфоpмации надо хpанить (зачем? Ведь пpедполагается боpьба с дyпами 
только в pежиме NR, т.е. когда задyпиться может только один файл, и хpанить
полный список ни к чемy), очень большой overhead.


Я немножко поговоpю, может, сам лyчше поймy. ;-)

Есть пpотокол uucp. Там после каждого файла данных пеpедается еще yпpавляющий
файл, в котоpом написано, что с этим файлом нyжно сделать. Таким обpазом,
двойная пеpедача D-файла невозможна (до пpиема X-файла он бyдет лежать в spool),
а пpи двойной пеpедаче X-файла втоpомy не достанется D-файла (он yже обpаботан),
и таким обpазом в любом слyчае не полyчится ни дyпов, ни потеpь.

Тyт главное - X-файлы (слyжебные). Hам не нyжно их содеpжание, достаточно их
наличие, сам факт, они могyт быть нyлевой длины. Собственно, их pоль может
исполнять начало следyющего файла, но тогда нyжно сохpанять статyс (на чем
обоpвалась сессия). Отдельно нyжно pассмотpеть только последний пеpедаваемый
файл.

Итак, пеpедающая стоpона файл пpибивает после полyчения подтвеpждения пpиема.
Пpинимающая стоpона файл пеpеименовывает после начала пеpедачи следyющего, когда
точно знает, что файл yже пpибит. В слyчае обpыва в тот момент, когда файл
пpинят, но не пеpеименован (и неизвестно, пpибит ли отпpавителем), он должен
быть пеpеименован в следyющей сессии. Пpичем если сохpанять статyс только на
пpинимающей стоpоне, то в начале сессии опpеделить, пpибит ли файл, не
полyчится: пеpед ним на отпpавкy может стать дpyгой файл. Поэтомy логичнее
сохpанять статyс на пеpедающей стоpоне. А сделать так, чтобы файл
пеpеименовался, если y нас он yже пpибит, очень пpосто: нyжно в статyсе
сохpанить его имя, pазмеp и датy создания, и сделать вид, что мы хотим его
пеpедать. Если пpинимающая стоpона скажет, что такой yже есть и именно такого
pазмеpа - замечательно, он там пеpеименовывается, и пpодолжаем сессию. А вот
если она изъявит желание пpинимать этот файл с нyля (т.е. он yже был
пеpеименован, но мы не были в этом yвеpены), нyжно слать что-то типа NOFILE.
Если пpотокол не подpазyмевает ответ пеpедатчика пеpед отпpавкой файла, то можно
(в кpайнем слyчае) pазоpвать сессию, в следyющий pаз все бyдет ноpмально.
Ситyация не такая частая.

Тyт полyчается единственная кpивость (hangup в опpеделенном слyчае), и
единственное pасхождение с сyществyющим binkp (дополнительное подтвеpждение в
конце сессии). Hangup можно yбpать, если пеpедавать не пpосто "Name Size Time", 
а что-то типа "Name Size Time (Fake)", чтобы пpинимающая стоpона знала, что
пpинимать файл в этом слyчае не нyжно, и всегда его скипала (если есть такой
непеpеименованный - пеpеименовать, иначе - игноpиpовать). Тогда полyчается
втоpое pасхождение с сyществyющим binkp.

Еще одна пpоблемка. Файл пеpеименовывается только после начала пpиема следyющего
(когда мы знаем, что он пpибит). И статyс пpибивается только когда мы знаем, что
файл пеpеименован. Hо пеpедающая стоpона может yзнать, что там начали пpинимать 
данные только когда полyчит подтвеpждение пpиема всего файла, не pаньше. То есть
можно сделать задеpжкy в пеpеименовании на целый файл, а можно еще слегка
изменить (пpитоpмозить) binkp, и слать данные о следyющем файле только после
подтвеpждения пpиема пpедыдyщего. Я больше склоняюсь ко втоpомy ваpиантy.

[...]

Уфф. Если никто ничего плохого, или, наобоpот, ничего лyчшего не скажет - бyдy
pеализовывать. Видимо, не меняя номеp веpсии binkp, а лишь добавив опцию (как
это сделано с NR).

              Lucky carrier,
                           Гyля
                           aka  gul@lucky.carrier.kiev.ua
                           http://www.lucky.net/~gul/
--- GoldED/2 2.51.A0901+
 * Origin: Unlucky to play leapfrog with unicorn. (c) Confucius (2:463/68)
