I wrote something about kmtp. Which is funny, because I still had not even started writing kmtp server in kme
This commit is contained in:
commit
87f6c8435f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.pdf
|
||||
|
129
head.typ
Normal file
129
head.typ
Normal file
@ -0,0 +1,129 @@
|
||||
#include calc
|
||||
|
||||
#let bold(cnt) = text(weight: "bold", cnt)
|
||||
#let MyStyle(doc) = [
|
||||
#set page(margin: (left:10mm, right: 10mm, top: 5mm, bottom: 5mm))
|
||||
#set text(size: 15pt, hyphenate: false)
|
||||
#doc
|
||||
]
|
||||
|
||||
#let rnd-next-seed(seed) = {
|
||||
let a = 1664525
|
||||
let c = 1013904223
|
||||
let m = 4294967296
|
||||
return calc.rem(a * seed + c, m)
|
||||
}
|
||||
|
||||
#let rnd-number-in-range(seed, L, R) = {
|
||||
if L >= R{
|
||||
panic("This is wrong")
|
||||
}
|
||||
let S = R - L;
|
||||
let seed2 = rnd-next-seed(seed)
|
||||
(seed2, L + calc.rem(seed2, S) + L)
|
||||
}
|
||||
|
||||
#let rnd-float(seed) = {
|
||||
let seed2 = rnd-next-seed(seed)
|
||||
let F = float(calc.rem(seed2, 65536)) / (65536 - 1)
|
||||
(seed2, F)
|
||||
}
|
||||
|
||||
#let rnd-vec2(seed, amplitude) = {
|
||||
let (seed, F1) = rnd-float(seed)
|
||||
let (seed, F2) = rnd-float(seed)
|
||||
(seed, (F1 * amplitude, F2 * amplitude))
|
||||
}
|
||||
|
||||
#let add-vec2(A, B) = (A.at(0) + B.at(0), A.at(1) + B.at(1))
|
||||
|
||||
#let float-to-edges(x) = if (x < 0.5) {calc.pow(x, 2)} else {1 - calc.pow(1 - x, 2)}
|
||||
|
||||
#let greenSunEffect(body) = text(weight: "bold", fill: green, font: "Pixeboy", body)
|
||||
|
||||
|
||||
#let genLightningRoot(width, height, seed) = {
|
||||
let roots = ()
|
||||
let kb = 1
|
||||
let spb = 6
|
||||
let bpb = spb * 3
|
||||
let i = kb * 1pt
|
||||
let n = 0
|
||||
while i + kb*1pt < width{
|
||||
if (i + (bpb + spb + kb)*1pt > width or n > 1000){
|
||||
i = width - kb*1pt
|
||||
}
|
||||
|
||||
let y_RND = 0
|
||||
(seed, y_RND) = rnd-float(seed)
|
||||
let y = (float-to-edges(y_RND) * 0.8 + 0.1) * height
|
||||
|
||||
roots.push((i, y))
|
||||
|
||||
let x_RND = 0
|
||||
(seed, x_RND) = rnd-number-in-range(seed, spb, bpb)
|
||||
i += (x_RND * 1pt)
|
||||
n += 1
|
||||
}
|
||||
(seed, roots)
|
||||
}
|
||||
|
||||
#let genLightningSpark(start, amplitude, seed, is-g) = {
|
||||
let sparks = 0
|
||||
if (is-g) {
|
||||
sparks = 2
|
||||
} else {
|
||||
let td_RND;
|
||||
(seed, td_RND) = rnd-number-in-range(seed, 1, 9)
|
||||
if (7 <= td_RND and td_RND <= 8){
|
||||
sparks = 1
|
||||
} else if (td_RND == 9){
|
||||
sparks = 2
|
||||
}
|
||||
}
|
||||
let res = ()
|
||||
for I in range(0, sparks){
|
||||
let curP = start
|
||||
let one_line = (start,)
|
||||
for j in range(0, 2){
|
||||
let step;
|
||||
(seed, step) = rnd-vec2(seed, amplitude)
|
||||
curP = add-vec2(curP, step)
|
||||
one_line.push(curP)
|
||||
}
|
||||
res.push(one_line)
|
||||
}
|
||||
(seed, res)
|
||||
}
|
||||
|
||||
// Returns array of paths
|
||||
#let genLightning(width, height, seed) = {
|
||||
let (seed, root) = genLightningRoot(width, height, seed)
|
||||
let paths = (root,)
|
||||
let N = root.len()
|
||||
for i in range(0, N){
|
||||
let start = root.at(i)
|
||||
let is-g = ((i == 0) or (i + 1 == N))
|
||||
let amplitude = if (is-g) {height / 3} else {height / 2}
|
||||
let new_lines
|
||||
(seed, new_lines) = genLightningSpark(start, amplitude, seed, is-g)
|
||||
for new_line in new_lines {
|
||||
paths.push(new_line)
|
||||
}
|
||||
}
|
||||
paths
|
||||
}
|
||||
|
||||
#let lightningBackground(body, seed) = context {
|
||||
let dim = measure(body)
|
||||
let points = genLightning(dim.width, dim.height, seed)
|
||||
// return points
|
||||
box(inset:0pt, outset:0pt)[
|
||||
#for lpath in points{
|
||||
place(top+left, dx: 0pt, dy: 0pt, path(stroke: yellow+1pt, ..lpath))
|
||||
}
|
||||
#body
|
||||
]
|
||||
}
|
||||
|
||||
#let sparkingGreenSunEffect(seed, body) = lightningBackground(greenSunEffect(body), seed)
|
266
kmtp.typ
Normal file
266
kmtp.typ
Normal file
@ -0,0 +1,266 @@
|
||||
#import "head.typ" as head : *
|
||||
|
||||
#show: MyStyle
|
||||
|
||||
|
||||
= KMTP Specification
|
||||
|
||||
== Область применения
|
||||
|
||||
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
|
||||
|
||||
Ключевым элементом данного протокола является структура байт под названием
|
||||
"сообщение". Они бывают двух видов: "запросы" (отправляются клиентом) и
|
||||
"ответы" (отправляются сервером). Но есть ещё одно деление: на "мастер-сообщение"
|
||||
и "суб-сообщение" (применимое к обоим типам адресанта сообщения).
|
||||
Контент каждого канала начинается с мастер-сообщения своего типа, а все
|
||||
последующие сообщения, отправленные по каналу, (если таковые есть)
|
||||
записаны в формате "суб-сообщений".
|
||||
У каждого типа есть свой формат, форматы различаются набором обязатльных "полей".
|
||||
|
||||
Каждый kmtp-канал содержит последовательность kmtp-сообщений. Первое сообщение -
|
||||
мастер-сообщение, остальные - суб-сообщения.
|
||||
При этом все суб-сообщения в канале как-бы считаются телом мастер-сообщения.
|
||||
Поэтому если есть поток суб-сообщений, приложенный к мастер-сообщению, то другого
|
||||
тела у мастер сообщения нет. Есть специальное поле заголовка мастер-сообщения,
|
||||
сообщающее, идёт ли после мастер-сообщения поток суб-сообщений. О поляъ расскажу позже.
|
||||
Поток суб-сообщений оканчивается когда вместо следующего сообщения отправитель передаёт
|
||||
по каналу LF (line feed). Опять же, если потока нет, о передавать этот LF
|
||||
в конце не нужно.
|
||||
|
||||
Сообщения содержат заголовок, чей формат полность определён и тело, называемое "кладом".
|
||||
Клад бывает 4 типов: отсутствующий (none) - это когда клад отсутствует, "письмо" (letter) -
|
||||
это когда клад имеет заранее известный (из заголовка сообщения) конечный размер,
|
||||
"вкид" (vkid) - это когда клад продолжается до самого конца канала без возможности
|
||||
вернуться обратно к kmtp-протоколу, при чем бесконечность вкида известна заранее. А ещё есть
|
||||
"отвал" (otval) - это когда размер клада заранее неизвестен и должен быть на ходу
|
||||
определён тем, кто принимает отвал. После перехода на отвал канал ещё может обратно
|
||||
вернуться к протоколу kmtp, однако определить этот момент возврата не понимая
|
||||
протокола, который использует отвал - невозможно. Потому при появлении в канале отвала
|
||||
любой прокси, что не понимает протокол отвала сразу потеряет возможность распознавать весь
|
||||
дальнейший трафик.
|
||||
Поток суб-сообщений - это вклад. А именно "вкид". Чтобы указать принимающему
|
||||
что вкид нашего мастер-сообщения это имеенно поток суб-сообщений, надо в специальное
|
||||
поле для типа клада записать "sub-kmtp". При этом если клиент отправит поле типа клада
|
||||
"sub-kmtp", все понимают, что он сейчас начнёт отправлять именно поток суб-запросов,
|
||||
а у сервера значение значения "sub-kmtp" в этои поле будет означать отправку потока
|
||||
суб-ответов.
|
||||
|
||||
Клиент посылает серверу запросы и на каждый запрос приходит ровно один ответ
|
||||
(в том же порядке, в котором соответствующие запросы были заданы). На
|
||||
мастер-запрос (master-request) приходит мастер-ответ (master-response),
|
||||
на суб-запрос (sub-request) приходит суб-ответ (sub-response).
|
||||
|
||||
Но сервер также может в любой момент времени отправить в выходной канал особый вид
|
||||
сообщения, называемое "событие" (event). Событие адресовано мастер-запросу, а не
|
||||
суб-запросам. События имеют такую же структуру, что и суб-ответы, отличаются они только тем,
|
||||
что в заголовке события "поле Status" обязано быть равно "Event (DC)".
|
||||
О том что такое "поле Status" будет сказано позже.
|
||||
|
||||
== Формат сообщения
|
||||
|
||||
Любое сообщение начинается с заголовка.
|
||||
Заголовок начинается с нескольких обязательных полей. Их число, назначение и формат заранее
|
||||
определены для каждого типа сообщений.
|
||||
После блока обязательных полей следует множество именованных полей. Их число не определено,
|
||||
пользователи kmtp вольны сами придумывать эти поля.
|
||||
Оканчивается заголовок символом LF (Перевод строки). После заголовка идёт клад.
|
||||
Если клад не none и клад окончился возвращением к 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[Client-Klad-Seek-Pos]: Формат тот же что и у Client-Klad-Sek-Pos
|
||||
(#math.penta.filled). Но здесь указывается сдвиг относительно ресурса на сервере.
|
||||
Так, к примеру, клиент может указать с какого момента продолжить скачивание большого
|
||||
файла с сервера.
|
||||
+ #field-name-decl[Body-Type]: если за сообщением следует "тело",
|
||||
то здесь указывается как
|
||||
его прочитать. Это может быть пустая строка, если тела нет, число в 16-ричной
|
||||
системе счисления, если произошел отнюх, "otval" если произошел отвал,
|
||||
"vkid", если произошел вкид (как можешь убедиться, эти 4 случая невозможно
|
||||
перепутать) #math.penta.filled. Важно! Если Body-Type пуст, то клада нет, сообщение
|
||||
оканчивается там, где оканчивается его заголовок. Но если Body-Type это "0" или
|
||||
"00 ... 0", то клад есть, го тип - письмо, и после пустого письма должен стоять
|
||||
обязательный перевод строки. Да, как бы это не было странно, но у суб-сообщений
|
||||
может быть свой клад, получается что клад суб-сообщения это клад в кладе
|
||||
мастер-сообщения.
|
||||
+ #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. Нет ограничения на размер этого числа.
|
||||
|
||||
Мастер-ответ отличается от суб-ответа тем, что в начале присутствует одно мастер поле:
|
||||
+ #master-field-name-decl[Auth-Password]: Это то, что ты подумал.
|
||||
|
||||
Status мастер-ответа не может быть #roman-status[DC] (Event).
|
||||
|
||||
Получается, что список обязательных полей, использующих синтаксис `=HEX>str or 'str'`,
|
||||
который дефолтен для именованных полей, довольно мал: это все мастер-поля + поле
|
||||
Location. Нужно учесть, что значение поля Location не должно содержать октета 0.
|
||||
|
||||
Этот документ сам определяет ряд именованных полей сообщения.
|
||||
Есть целый ряд незапланированных полей, вида `"Proxy-" + n + "-" + Name`,
|
||||
где n - натуральное число, а Name - имя для поля. Это будет поле, переданное
|
||||
n-ым с конца прокси (отсчет начинается от стороны получателя). От одного прокси может
|
||||
передаваться много разных полей, поэтому можно дополнительно указать суб-имя `Name`.
|
||||
|
||||
+ #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)\
|
||||
Запрашиваемый ресурс / запрашиваямая операция над ресурсом недопустима в стране
|
||||
клиента. Отличие от DCCLII в том, что другим клиентам из других стран этот же
|
||||
ресурс может быть доступен.
|
||||
|
||||
== Широко известные функции. Поле Function.
|
||||
|
||||
=== Предисловие
|
||||
|
||||
Множество кодов статуса ответа зафиксировано и не расширяемо, а вот свои функции серверу
|
||||
придумывать не запрещено. Есть некоторые принятые значения для определённых
|
||||
имён функций. Здесь представлен неисчерпывающий список таких имён функций.
|
||||
|
||||
=== read
|
||||
|
||||
Читает весь ресурс (файл) или его часть. Для указания интересующего нас постфикса
|
||||
клиент заполняет своё поле Resource-Klad-Seek-Pos.
|
Loading…
Reference in New Issue
Block a user