commit 87f6c8435f2d55cc031d97252b4289032bae7549 Author: Andreev Gregory <1@example.com> Date: Sun Nov 10 20:37:42 2024 +0300 I wrote something about kmtp. Which is funny, because I still had not even started writing kmtp server in kme diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd98a73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pdf + diff --git a/2e1m.typ b/2e1m.typ new file mode 100644 index 0000000..227f5bd --- /dev/null +++ b/2e1m.typ @@ -0,0 +1,3 @@ +#import "head.typ" as head : * + +#show: MyStyle \ No newline at end of file diff --git a/head.typ b/head.typ new file mode 100644 index 0000000..119b7d8 --- /dev/null +++ b/head.typ @@ -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) \ No newline at end of file diff --git a/kmon.typ b/kmon.typ new file mode 100644 index 0000000..227f5bd --- /dev/null +++ b/kmon.typ @@ -0,0 +1,3 @@ +#import "head.typ" as head : * + +#show: MyStyle \ No newline at end of file diff --git a/kmtp.typ b/kmtp.typ new file mode 100644 index 0000000..2748550 --- /dev/null +++ b/kmtp.typ @@ -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. \ No newline at end of file