kme-specific-proto-spec/kmtp.typ

263 lines
21 KiB
Plaintext
Raw Permalink Normal View History

#import "head.typ" as head : *
#show: MyStyle
= KMTP, Sub-KMTP Спецификация
== Область применения
KMTP это протокол, созданный специально для будущей видеоигры KM (Используется
в рамках KM project). Он нацелен на замену чрезвычайно раздутого
протокола http+websocket. KMTP (KM Transport Protocol) это его официальное название,
однако в самом мире игры KM он известен как STP (Suckless
Transport Protocol).
KMTP должен использоваться поверх надёжного транспортного протокола,
создающего двунаправленный поток байт,
такого как TCP, так как даже малейшая ошибка может привести к непредсказуемым последствиям.
KMTP применяется когда между двумя общающимися процессами
выделено отношение "клиент-сервер". KMTP предполагает, что на сервере расположено
множество файлов (ресурсов) в древовидной структуре, а клиент обращается к ним,
сообщая какую функцию он хочет произвести над тем или иным ресурсом. После этого
возможны передача серверу
потока байт (аргумент функции) и приём от сервера потока байт (результат функции).
Очень часто kmtp будет применяться как обёртка для протокола более высокого уровня,
хотя под это понятие попадает даже содержимое jpeg картинки, скачанной с файлового
сервера.
В начале соединения клиент может предствиться передав "логин" и "пароль".
Каналы связи называются
- client's output / server's output / request channel
- client's input / servers's output / response channel
Ключевым элементом данного протокола является структура байт под названием
"сообщение". Они бывают двух видов: "запросы" (отправляются клиентом) и
"ответы" (отправляются сервером). Но есть ещё одно деление: на "мастер-сообщение"
и "суб-сообщение" (применимое к обоим типам адресанта сообщения).
Контент каждого канала начинается с мастер-сообщения своего типа.
Если клиент и сервер договорились сделать "апгрейд" до sub-kmtp, то все дальнейшие
сообщения будут записаны в формате "суб-сообщений".
У каждого типа есть свой формат, форматы различаются набором обязательных "полей".
Каждый канал содержит только мастер-сообщение (соответсвующего типа) и ничего более.
Поток суб-сообений это часть местер сообщения. Естесственно, поток суб-сообщений
будет присутсвовать только когда был произведён апгрейд до sub-kmtp.
В этом случае сначала идёт мастер-заголовок сообщения, а всё остальное (та часть
сообщения, называющаяся кладом) - это sub-kmtp. Вместо него, конечно же, мог бы быть
совсем другой клад мастер сообщения.
Сообщения содержат заголовок, чей формат полность определён и тело, называемое "кладом".
Клад бывает 4 типов: "письмо" (letter) -
это когда клад имеет заранее известный (из заголовка сообщения) конечный размер,
"вкид" (vkid) - это когда клад продолжается до самого конца канала без возможности
вернуться обратно к kmtp-протоколу, при чем бесконечность вкида известна заранее. А ещё есть
"отвал" (otval) - это когда размер клада заранее неизвестен и должен быть на ходу
определён тем, кто принимает отвал. После перехода на отвал канал ещё может обратно
вернуться к протоколу sub-kmtp, однако определить этот момент возврата не понимая
протокола, который использует отвал - невозможно. Потому при появлении в канале отвала
любой прокси, что не понимает протокол отвала сразу потеряет возможность распознавать весь
дальнейший трафик. Как можно заметить практического смысла использовать отвал
вместо вкида в мастер-kmtp сообщении нет.
Поток суб-сообщений - это клад. А именно "отвал". Чтобы указать принимающему
что вкид нашего мастер-сообщения это имеенно поток суб-сообщений, надо в специальное
поле для типа клада записать "sub-kmtp". При этом если клиент отправит поле типа клада
"sub-kmtp", все понимают, что он сейчас начнёт отправлять именно поток суб-запросов,
а у сервера значение значения "sub-kmtp" в этои поле будет означать отправку потока
суб-ответов.
Клиент посылает серверу запросы и на каждый запрос приходит ровно один ответ
(в том же порядке, в котором соответствующие запросы были заданы). На
мастер-запрос (master-request) приходит мастер-ответ (master-response),
на суб-запрос (sub-request) приходит суб-ответ (sub-response).
Но! Сервер также может в любой момент времени отправить в выходной канал особый вид
сообщения, называемое "событие" (event). Событие адресовано мастер-запросу, а не
суб-запросам. События имеют такую же структуру, что и суб-ответы, отличаются они только тем,
что в заголовке события "поле Status" обязано быть равно "Event (DC)".
О том что такое "поле Status" будет сказано позже.
== Формат сообщения
Любое сообщение начинается с заголовка.
Заголовок начинается с нескольких обязательных полей. Их число, назначение и формат заранее
определены для каждого типа сообщений.
После блока обязательных полей следует множество именованных полей. Их число не определено,
пользователи kmtp вольны сами придумывать эти поля.
Оканчивается заголовок символом LF (Перевод строки). После заголовка идёт клад.
Поля имеют формат "имя" + ":"/">" + "значение" + LF. При этом есть безымянные поля, где
"имя" пусто.
"имя" это строка из символов `[a-zA-Z0-9-_+=/]` , что овпадает с форматом
ключей в kmon (о kmon можно прочитать в соответствующем документе).
Имя и значение разделены символом ":", но для некоторых 3-ёх обязательных полей
":" заменяется на ">". Эти поля: "Virtual-Host", "Auth-Username", "Auth-Password".
Они отличаются от других тем, что они присутствуют только в мастер-сообщениях.
Об этом позже будет рассказано.
"Значение" несёт в себе строку произвольного формата, но важно то, как она обёрнута.
Если надо передать строку str, значение записывается как "=" + hex(len(str)) + ">" + str,
при этом если так получилось, что str не содержит символа `'` (ascii 0x27), то можно
записать значение как "'" + str + "'". Семантика сообщения от выбора способа записи
значения не меняется. Такой формат совпадает с форматом строк в kmon.
Некоторые обязательные поля, о которых известно, что они не содержат символа перевода строки,
передаются без форматирования, напрямую: "имя" + ":" + str + LF.
Поля, которые имеют особую запись будут помечены в списке полей символом #math.penta.filled .
Сейчас будут полность определены обязательне поля всех 4 типов сообщений.
Хотя им всем даны имена, в канале они опущены, ибо известны заранее.
#let field-name-decl(name) = bold(name)
#let master-field-name-decl(name) = bold(name)
Устройство блока заголовка суб-запроса:
+ #field-name-decl[Location]: Путь нужного ресурса на сервере. В мастер-запросе
не может быть относительным путём, ведь нам неотчего отталкиваться. Правила такие же,
как для юникс-путя.
+ #field-name-decl[Function]: Функция, производимая над ресурсом.
Ближайшим аналогом будет
поле method из http. KMTP определяет ряд функций с определённым значение,
однако пользователи kmtp могут сами определять свои функции, если сервер
сможет их понять. Function имеет ограничение на используемые символы (функции
именуются как ключи в kmon), что делает ненужным оборачивание в кавычки.
(#math.penta.filled)
+ #field-name-decl[Client-Klad-Seek-Pos]: В этом поле записывается неотрицательное
число (в шестнадцатиричной системе счисления) #math.penta.filled. Пустая строка
эквивалентна нулю. Значение зависит от функции, но принято использовать его для
указания сдвига данных, передаваемых в кладе клиента (например, если клиент
загружал большой файл, но соединение оборвалось, это поле можно использовать
чтобы указать с какого места продолжается закачка).
+ #field-name-decl[Resource-Klad-Seek-Pos]: Формат тот же что и у Client-Klad-Sek-Pos
(#math.penta.filled). Но здесь указывается сдвиг относительно ресурса на сервере.
Так, к примеру, клиент может указать с какого момента продолжить скачивание большого
файла с сервера.
+ #field-name-decl[Body-Type]: если за сообщением следует "тело",
то здесь указывается как
его прочитать. Это может быть число в 16-ричной
системе счисления, если передано письмо, "otval" если произошел отвал,
"vkid", если произошел вкид (как можешь убедиться, эти 3 случая невозможно
перепутать) #math.penta.filled. Да, как бы это не было странно, но у суб-сообщений
может быть свой клад, получается что клад суб-сообщения это клад в кладе
мастер-сообщения.
+ #bold[Upgrade-Name]: Здесь указывается узнаваемое имя протокола,
к которому мы переходим. На это поле наложены такие же ограничения, как
на поле Function (используются только символы, допустимые в kmon
ключе), поэтому кавычки не используются #math.penta.filled. Пустая строка допустима.
Имеет смысл только если клад не пуст.
Для удобства мастер-запрос очень похож на суб-запрос. Различие лишь в том, что
в мастер-запрос в начало добавляются 3 мастер-поля:
+ #master-field-name-decl[Virtual-Host]
+ #master-field-name-decl[Auth-Username]
+ #master-field-name-decl[Auth-Password]
Каждое из них это то что ты подумал это это. Мастер-поле в сериализованном виде
начинается не с ":", как все обязательные поля, а с ">".
В мастер-запросе поле Location не может быть относительным путём, а в суб-запросах
может, точкой отсчета для относительных путей будет путь указанный в мастер-запросе.
#let roman-status(x) = bold(x)
Усройство блока заголовка суб-ответа:
+ #field-name-decl[Status]: одна строка из определённого набора, необходимая для
правильной интерпретации ответа. #math.penta.filled.
+ #field-name-decl[Resource-Klad-Seek-Pos]: #math.penta.filled.
+ #field-name-decl[Client-Klad-Seek-Pos]: #math.penta.filled.
+ #field-name-decl[Body-Type]: #math.penta.filled.
+ #field-name-decl[Upgrade-Name]: #math.penta.filled.
+ #field-name-decl[Last-Change-Time]: число (шестнадцатеричное),
отмеряющее время последнего изменения ресурса.
Не обязано быть привязано к реальному времени. Может быть пустой строкой - это
эквивалентно нулю. #math.penta.filled. Нет ограничения на размер этого числа.
Мастер-ответ ничем не отличается от суб-ответа.
Status мастер-ответа не может быть #roman-status[DC] (Event).
Получается, что список обязательных полей, использующих синтаксис `=HEX>str or 'str'`,
который дефолтен для именованных полей, довольно мал: это все мастер-поля + поле
Location. Нужно учесть, что значение поля Location не должно содержать октета 0,
хотя это наврятле будет проверяться.
Этот документ сам определяет ряд именованных полей сообщения.
Есть целый ряд незапланированных полей, вида `"Proxy-" + n + "-" + Name`,
где n - натуральное число, а Name - имя для поля. Это будет поле, переданное
n-ым с конца прокси (отсчет начинается от стороны получателя). От одного прокси может
передаваться много разных полей, поэтому можно дополнительно указать суб-имя `Name`.
Считается, что kmtp-прокси при прохождении через него заголовка сообщения, порядок
всех полей, начинающихся с `"Proxy-"`, инкрементирует, а если хочет и свои поля
добавить, Поставит в их имена префикс `"Proxy-1-"`.
+ #field-name-decl[Proxy-n-Address]: Адрес клиента, который видит прокси, но конечный
сервер не видит. Здесь #field-name-decl[Proxy-n-Address] - это параметризованное имя
поля.
== Коды статуса. Поле Status
=== Предисловие
У каждого сообщения сервера (ответа или события) поле Status
(код статуса) принимает одно
из возможных значений. Сейчас будут перечислены все допустимые значения
статуса ответа и их смысл.
=== Самые частые коды статуса, выражающие успех (CC..., DC...)
- #roman-status[CC] (Success)
- #roman-status[DC] (Event)
=== Коды перенаправления (CCC...)
- #roman-status[CCCI] (Moved Permanently)
- #roman-status[CCCIII] (See other)
- #roman-status[CCCVII] (Temporary Redirect)
- #roman-status[CCCX] (Go Back)
#heading(level: 3)[Ошибка когда виноват клиент (CD...)]
- #roman-status[CD] (Bad request)
- #roman-status[CDI] (Unauthorized)
- #roman-status[CDIII] (Forbidden)
- #roman-status[CDIV] (Not Found)
- #roman-status[CDV] (Inadmissible Function)
- #roman-status[CDX] (Gone)
- #roman-status[CDXIII] (Klad Too Long)
- #roman-status[CDXIV] (Path Too Long)
- #roman-status[CDXV] (Unsupported Media Type)
- #roman-status[CDXXXI] (Header Too Long)
- #roman-status[CDLXI] (Profile Not Found)
- #roman-status[CDLXII] (Incorrect Seek Pos Client Side)
- #roman-status[CDLXIV] (Inadmissible Body Type)
- #roman-status[CDLXXVI] (Incorrect Seek Pos Resource Side)
#heading(level: 3)[Ошибка когда виноват сервер, прокси или машина, на которой
они работают (D...)]
- #roman-status[D] (Internal Server Error)
- #roman-status[DII] (Destination Unreachable)
- #roman-status[DIII] (Service Unavailable)
- #roman-status[DXI] (System Failure)
- #roman-status[DXII] (Undelivered)
- #sparkingGreenSunEffect(1)[DXVIII]
(Nuclear Reactor That Was Managed By This Computer Was Blown Up By Terrorists) \
Упс, это спойлеры к KM.
#heading(level: 3)[Ошибка когда виновато общество в котором вы живёте (DCC...)]
- #roman-status[DCCLI] (Unavailable For Legal Reasons) \
Запрашиваемый ресурс / запрашиваямая операция над ресурсом недопустима в стране
сервера.
- #roman-status[DCCLII] (Cancelled For Legal Reasons)\
Запрашиваемый ресурс / запрашиваямая операция над ресурсом недопустима в стране
клиента. Отличие от DCCLI в том, что другим клиентам из других стран этот же
ресурс может быть доступен.
== Широко известные функции. Поле Function.
=== Предисловие
Множество кодов статуса ответа зафиксировано и не расширяемо, а вот свои функции серверу
придумывать не запрещено. Есть некоторые принятые значения для определённых
имён функций. Здесь представлен неисчерпывающий список таких имён функций.
=== read
Читает весь ресурс (файл) или его часть. Для указания интересующего нас постфикса
клиент заполняет своё поле Resource-Klad-Seek-Pos.