11 Onion
Цель модуля onion в Tox заключается в том, чтобы предотвратить узнавание пирами, которые не являются друзьями, временного открытого ключа DHT из известного долгосрочного открытого ключа пира и предотвратить узнавание пирами долгосрочного открытого ключа пиров, когда известен только временный ключ DHT.Это гарантирует, что только друзья пира могут найти его и подключиться к нему, и косвенно гарантирует, что чужие друзья не смогут найти ip-адрес пира, зная Tox-адрес друга.
Единственный способ предотвратить ассоциацию между пирами в сети временного открытого ключа DHT и долгосрочного открытого ключа - не транслировать долгосрочный ключ и передавать открытый ключ DHT только тем, кто не является другом.
Onion позволяет друзьям посылать своим друзьям, чей настоящий открытый ключ им известен, поскольку он является частью идентификатора Tox, свой открытый ключ DHT, чтобы друзья могли найти и подключиться к ним без того, чтобы другие могли определить настоящие открытые ключи друзей.
Как же работает onion?
Onion работает, позволяя одноранговым пользователям объявлять свой настоящий открытый ключ одноранговым пользователям, проходя через " onion" путь. Это похоже на DHT, но через onion-пути. Фактически, он использует DHT для того, чтобы пиры могли найти пиров с идентификаторами, наиболее близкими к их публичному ключу, проходя через луковые пути.
Для того чтобы анонимно объявить свой настоящий открытый ключ в сети Tox с использованием лука, пир сначала выбирает 3 случайных узла, которые он знает (они могут быть откуда угодно: из DHT, подключенных TCP-релей или узлов, найденных при поиске пиров с помощью лука). Узлы должны быть выбраны таким образом, чтобы исключить вероятность того, что ими управляет один и тот же человек, возможно, путем просмотра ip-адресов и проверки, находятся ли они в одной подсети, или другими способами. Необходимо провести дополнительные исследования, чтобы убедиться, что узлы выбираются наиболее безопасным способом.
Причина для 3 узлов заключается в том, что 3 прыжка - это то, что используется в Tor и других анонимных сетях на основе onion.
Эти узлы называются узлами A, B и C. Обратите внимание, что если пир не может общаться через UDP, его первым пиром будет один из TCP-реле, к которому он подключен, и который будет использоваться для отправки его лукового пакета в сеть.
TCP-реле может быть только узлом A или первым peer в цепочке, так как TCP-реле по сути действует как шлюз в сеть. Данные, отправляемые модулю TCP Client для отправки в виде TCP onion-пакета, отличаются от данных, отправляемых напрямую через UDP. Это связано с тем, что его не нужно шифровать (соединение с сервером TCP relay уже зашифровано).
Сначала я объясню, как происходит обмен данными через луковые пакеты.
Примечание: nonce - это 24-байтовый nonce. Все вложенные nonce одинаковы с внешним nonce.
Луковый пакет (запрос):
Начальные (TCP) данные, отправленные как данные лукового пакета через клиентский модуль TCP:
IP_Port узла B
Случайный открытый ключ PK1
Зашифровано секретным ключом SK1 и открытым ключом узла B, а также nonce:
IP_Port узла C
Случайный открытый ключ PK2
Зашифрован секретным ключом SK2, открытым ключом узла C и nonce:
IP_Port узла D
Данные для отправки в узел D
Начальный (UDP) (отправленный от нас к узлу A):
uint8_t (0x80) идентификатор пакета
Nonce
Наш временный открытый ключ DHT
Зашифровано с помощью нашего временного секретного ключа DHT, открытого ключа узла A и nonce:
IP_Port узла B
Случайный открытый ключ PK1
Шифруется с помощью секретного ключа SK1 и открытого ключа узла B и nonce:
IP_порт узла C
Случайный открытый ключ PK2
Зашифрован секретным ключом SK2, открытым ключом узла C и nonce:
IP_Port узла D
Данные для отправки в узел D
(отправленные от узла A к узлу B):
uint8_t (0x81) идентификатор пакета
Nonce
Случайный открытый ключ PK1
Шифруется секретным ключом SK1, открытым ключом узла B и nonce:
IP_Port узла C
Случайный открытый ключ PK2
Зашифрован секретным ключом SK2, открытым ключом узла C и nonce:
IP_Port узла D
Данные для отправки на узел D
Nonce
Шифруется временным симметричным ключом узла A и Nonce
IP_Port (нас)
(отправляется с узла B на узел C):
uint8_t (0x82) идентификатор пакета
Nonce
Случайный открытый ключ PK1
Зашифрован секретным ключом SK1 и открытым ключом узла C, а также nonce:
IP_Port узла D
Данные для отправки на узел D
Nonce
Шифруется временным симметричным ключом узла B и нnonce:
IP_Port (узла A)
Nonce
Зашифрован временным симметричным ключом узла A и nonce:
IP_Port (нас)
(отправлено с узла C на узел D):
Данные для отправки в узел D
Nonce
Зашифрован временным симметричным ключом узла C и nonce:
IP_Port (узла B)
Nonce
Зашифрован временным симметричным ключом узла B и nonce:
IP_Port (узла A)
Nonce
Зашифрован временным симметричным ключом узла A и nonce:
IP_Port (от нас)
Луковый пакет (ответ):
первоначальный (отправлен от узла D к узлу C):
uint8_t (0x8c) идентификатор пакета
Nonce
Зашифрован временным симметричным ключом узла C и nonce:
IP_Port (узла B)
Nonce
Зашифрован временным симметричным ключом узла B и nonce:
IP_Port (узла A)
Nonce
Зашифрован временным симметричным ключом узла A и nonce:
IP_Port (нас)
Данные для отправки обратно
(отправленные от узла C к узлу B):
uint8_t (0x8d) идентификатор пакета
Nonce
Шифруется временным симметричным ключом узла B и nonce:
IP_Port (узла A)
Nonce
Зашифрован временным симметричным ключом узла A и nonce:
IP_Port (нас)
Данные для отправки обратно
(отправленные от узла B к узлу A):
uint8_t (0x8e) идентификатор пакета
Nonce
Шифруется временным симметричным ключом узла A и nonce:
IP_Port (нас)
Данные для отправки обратно
(отправлено от узла A к нам):
Данные для отправки обратно
Каждый пакет шифруется несколько раз, так что только узел A сможет получить и расшифровать первый пакет и знать, куда его отправить, узел B сможет получить расшифрованный пакет, расшифровать его снова и знать, куда его отправить, и так далее. Вы также заметите кусок зашифрованных данных (sendback) в конце пакета, который становится все больше и больше на каждом уровне с IP предыдущего узла в нем. Именно так узел, получающий конечные данные (узел D), сможет отправить данные обратно.
Когда пир получает пакет onion, он расшифровывает его, шифрует координаты (IP/порт) источника вместе с уже существующими зашифрованными данными (если они существуют) с помощью симметричного ключа, известного только пиру и обновляемого каждый час (в toxcore) в качестве меры безопасности для принудительного истечения маршрутов.
Вот схема, как это работает:
peer
-> [onion1[onion2[onion3[data]]]] -> Node A
-> [onion2[onion3[data]]][sendbackA] -> Node B
-> [onion3[data]][sendbackB[sendbackA]] -> Node C
-> [data][SendbackC[sendbackB[sendbackA]]]-> Node D (end)
Node D
-> [SendbackC[sendbackB[sendbackA]]][response] -> Node C
-> [sendbackB[sendbackA]][response] -> Node B
-> [sendbackA][response] -> Node A
-> [response] -> peer
Случайные открытые ключи в луковых пакетах - это временные открытые ключи, созданные и используемые только для данного лукового пути. Это делается для того, чтобы другим было сложно связать вместе различные пути. Каждый зашифрованный слой должен иметь свой открытый ключ. Именно по этой причине в определениях пакетов, приведенных выше, имеется несколько ключей.
Nonce используется для шифрования всех уровней шифрования. Этот 24-байтовый nonce должен генерироваться случайным образом. Если он генерируется неслучайно и имеет связь с nonce, используемыми для других путей, то можно связать вместе различные луковые пути.
IP_Port - это ip и порт в упакованном формате:
| Length | Contents |
|---|---|
| 1 | TOX_AF_INET (2) for IPv4 or TOX_AF_INET6 (10) for IPv6 |
| 4 | 16 | IP address (4 bytes if IPv4, 16 if IPv6) |
| 12 | 0 | Zeroes |
| 2 | uint16_t Port |
В случае IPv4 формат дополняется 12 байтами нулей, чтобы IPv4 и IPv6 имели одинаковый размер.
IP_Port всегда будет иметь размер 19 байт. Это сделано для того, чтобы было трудно определить, ipv4 или ipv6 находится в пакете, просто посмотрев на размер. 12 байт нулей в ipv4 должны быть установлены в 0 и не должны оставаться неинициализированными, поскольку таким образом может произойти утечка информации, если они останутся неинициализированными. Все числа здесь в формате big endian.
IP_Port в данных sendback может быть в любом формате, лишь бы длина была 19 байт, так как только тот, кто его пишет, может расшифровать его и прочитать, однако, рекомендуется использовать предыдущий формат из-за повторного использования кода. Nonce в данных обратной связи должен быть 24-байтовым nonce.
Каждый слой лука имеет свой упакованный идентификатор, который идентифицирует его, чтобы реализация точно знала, как с ним работать. Обратите внимание, что любые данные, отправляемые обратно, должны быть зашифрованы, выглядеть случайными и не допускать утечки информации, так как все узлы на пути будут видеть их.
Если с полученными луковыми пакетами что-то не так (не удается расшифровать), реализация должна отбросить их.
Реализация должна иметь код для каждого типа пакета, который обрабатывает его, добавляет (или расшифровывает) обратный сигнал и отправляет его следующему узлу на пути. Пакетов много, но реализация должна быть очень простой.
Обратите внимание, что если первым узлом на пути является TCP-реле, TCP-реле должно поместить идентификатор (вместо IP/порта) в sendback, чтобы знать, что любой ответ должен быть отправлен соответствующему peer, подключенному к TCP-реле.
Это объясняет, как создавать луковые пакеты и как они отправляются обратно. Далее речь пойдет о том, что на самом деле отправляется и принимается поверх этих луковых пакетов или путей.
Примечание: nonce - это 24-байтовый nonce.
пакет запроса announce:
| Length | Contents |
|---|---|
| 1 | uint8_t (0x83) |
| 24 | Nonce |
| 32 | A public key (real or temporary) |
| ? | Payload |
Открытый ключ - это наш настоящий долгосрочный открытый ключ, если мы хотим объявить о себе, и временный, если мы ищем друзей.
Полезная нагрузка зашифрована частью секретного ключа отправленного открытого ключа, открытым ключом узла D и nonce, и содержит:
| Length | Contents |
|---|---|
| 32 | Ping ID |
| 32 | Public key we are searching for |
| 32 | Public key that we want those sending back data packets to use |
| 8 | Data to send back in response |
Если ping id равен нулю, ответьте пакетом announce response.
Если ping id совпадает с тем, который узел отправил в ответе announce, а открытый ключ совпадает с искомым, добавьте часть, используемую для отправки данных, в наш список. Если список полон, замените самую дальнюю запись.
данные в пакете запроса маршрутизации:
| Length | Contents |
|---|---|
| 1 | uint8_t (0x85) |
| 32 | Public key of destination node |
| 24 | Nonce |
| 32 | Temporary just generated public key |
| variable | Payload |
Полезная нагрузка шифруется с помощью временного секретного ключа, nonce и открытого ключа из пакета ответа анонса узла назначения. Если узел D содержит данные ret для узла, он отправляет материал в этом пакете в качестве данных для маршрутизации ответного пакета в нужный узел.
Данные в предыдущем пакете имеют формат:
| Length | Contents |
|---|---|
| 32 | Real public key of sender |
| variable | Payload |
Полезная нагрузка шифруется с помощью реального секретного ключа отправителя, nonce в пакете данных и реального открытого ключа получателя:
| Length | Contents |
|---|---|
| 1 | uint8_t id |
| variable | Data (optional) |
Возвратные данные
| Length | Contents |
|---|---|
| 1 | uint8_t (0x84) |
| 8 | Data to send back in response |
| 24 | Nonce |
| variable | Payload |
Полезная нагрузка шифруется с помощью секретного ключа DHT узла D, открытого ключа в запросе и nonce:
| Length | Contents |
|---|---|
| 1 | uint8_t is_stored |
| 32 | Ping ID or Public Key |
| variable | Maximum of 4 nodes in packed node format (see DHT) |
Пакет содержит ping ID, если is_stored равен 0 или 2, или открытый ключ, который должен быть использован для отправки пакетов данных, если is_stored равен 1.
Если is_stored не 0, это означает, что информация для получения искомого открытого ключа хранится на этом узле. is_stored равно 2 является ответом на попытку пира объявить о себе, чтобы сообщить пиру, что он в настоящее время успешно объявлен.
данные для пакета ответа маршрутизации:
| Length | Contents |
|---|---|
| 1 | uint8_t (0x86) |
| 24 | Nonce |
| 32 | Temporary just generated public key |
| variable | Payload |
Полезная нагрузка шифруется с помощью временного секретного ключа, nonce и открытого ключа из пакета ответа-объявления узла назначения.
Существует 2 типа пакетов запроса и 2 пакета ответа на них. Запрос announce используется для объявления о себе узлу, а пакет announce response используется узлом для ответа на этот пакет. Пакет запроса данных для маршрутизации - это пакет, используемый для отправки пакетов через узел другому узлу, который объявил о себе и который мы нашли. Пакет ответа "данные для маршрутизации" - это то, во что узел преобразует этот пакет.
Чтобы объявить о себе в сети, мы должны сначала найти, используя пакеты объявлений, пиров с открытым ключом DHT, наиболее близким к нашему реальному открытому ключу. Затем мы должны объявить о себе этим одноранговым пользователям. После этого друзья смогут отправлять нам сообщения, используя данные для маршрутизации пакетов, отправляя их этим одноранговым пользователям. Чтобы найти тех, кому мы объявили о себе, наши друзья найдут ближайших к нашему настоящему открытому ключу и спросят их, знают ли они нас. Затем они смогут использовать те пиры, которые нас знают, чтобы отправить нам несколько сообщений, содержащих их открытый ключ DHT (который нам нужно знать, чтобы напрямую подключиться к ним), TCP-реле, к которым они подключены (чтобы мы могли подключиться к ним с помощью этих релеев, если понадобится), и некоторые пиры DHT, к которым они подключены (чтобы мы могли быстрее найти их в DHT).
Пакеты запроса объявления - это те же самые пакеты, которые используются немного по-другому, если мы объявляем о себе или ищем пиров, которые знают одного из наших друзей.
Если мы объявляем о себе, мы должны поместить в пакет наш настоящий долгосрочный открытый ключ и зашифровать его нашим долгосрочным закрытым ключом. Это делается для того, чтобы пир, которому мы объявляем о себе, мог убедиться, что мы действительно владеем этим открытым ключом. Если мы ищем пиров, мы используем временный открытый ключ, который используется только для пакетов, ищущих этого пира, чтобы утечка информации была как можно меньше. ping_id - это 32-байтовое число, которое посылается нам в ответе на объявление, а мы должны отправить его обратно пиру в другом запросе на объявление. Это сделано для того, чтобы люди не могли легко объявлять о себе много раз, так как они должны доказать, что могут отвечать на пакеты от пира, прежде чем пир позволит им объявить о себе. Этот ping_id устанавливается в 0, если ничего не известно.
Открытый ключ, который мы ищем, устанавливается как наш долгосрочный открытый ключ, когда мы объявляем себя, и как долгосрочный открытый ключ друга, которого мы ищем, если мы ищем пиров.
Когда мы объявляем о себе, открытый ключ, который мы хотим, чтобы другие использовали для отправки нам данных, устанавливается как временный открытый ключ, и мы используем закрытую часть этого ключа для расшифровки данных маршрутизации пакетов, отправленных нам. Этот открытый ключ служит для того, чтобы предотвратить сохранение пирами старых данных маршрутизации пакетов из предыдущих сессий и возможность их повторного воспроизведения в будущих сессиях Tox. Этот ключ устанавливается на ноль при поиске пиров.
Данные sendback - это 8-байтовое число, которое будет отправлено обратно в ответе пакета announce. Его цель - узнать, на какой пакет запроса объявления отвечает ответ, и, следовательно, его местоположение в незашифрованной части ответа. Это необходимо в toxcore для поиска и проверки информации о пакете, чтобы расшифровать его и правильно обработать. Toxcore использует его как индекс для своего специального массива ping_array.
Почему бы нам не использовать разные пакеты вместо того, чтобы иметь один запрос пакета объявления и один ответ, который делает все? Так возможным злоумышленникам гораздо сложнее понять, объявляем ли мы о себе или ищем друзей, поскольку пакеты для обоих случаев выглядят одинаково и имеют одинаковый размер.
Незашифрованная часть пакета announce response содержит данные sendback, которые были отправлены в запросе, на который этот пакет отвечает, и 24-байтовый случайный nonce, используемый для шифрования зашифрованной части.
Число is_stored имеет значение 0, 1 или 2. 0 означает, что открытый ключ, который искался в запросе, не хранится и не известен этому аналогу. 1 означает, что он есть, а 2 означает, что мы успешно объявлены на этом узле. И 1, и 2 нужны для того, чтобы при перезапуске клиентов можно было повторно объявить, не дожидаясь тайм-аута предыдущего объявления. В противном случае это было бы невозможно, поскольку клиент получил бы ответ 1 без ping_id, который необходим для успешного повторного объявления.
Когда число is_stored равно 0 или 2, следующие 32 байта - это ping_id. Когда is_stored равен 1, он соответствует открытому ключу (открытому ключу отправки данных, установленному другом в его запросе объявления), который должен использоваться для шифрования и отправки данных другу.
Затем к ответу прикрепляются необязательные максимум 4 узла в формате упакованных узлов DHT (см. DHT), которые обозначают 4 пира DHT с открытыми ключами DHT, наиболее близкими к искомому открытому ключу в запросе объявления, известному пиру (см. DHT). Для поиска этих пиров toxcore использует ту же функцию, что и для поиска пиров для ответов get node DHT. Пиры, желающие объявить о себе или ищущие пиров, которые "знают" их друзей, будут рекурсивно запрашивать все более близких пиров, пока не найдут наиболее близкого из них, а затем либо объявят о себе им, либо просто будут пинговать их время от времени, чтобы узнать, можно ли связаться с их другом. Обратите внимание, что функция расстояния, используемая для этого, такая же, как и в Tox DHT.
Пакеты запроса данных для маршрутизации - это пакеты, используемые для отправки данных непосредственно другому peer через узел, который знает этого peer. Открытый ключ - это открытый ключ конечного пункта назначения, куда мы хотим отправить пакет (настоящий открытый ключ нашего друга). Nonce - это 24-байтовый случайный nonce, а открытый ключ - это случайный временный открытый ключ, используемый для шифрования данных в пакете и, если возможно, только для отправки пакетов этому другу (мы хотим, чтобы в сеть попадало как можно меньше информации, поэтому мы используем временные открытые ключи, так как не хотим, чтобы одноранговые пользователи видели одинаковые открытые ключи и могли связать все вместе). Данные - это зашифрованные данные, которые мы хотим отправить сверстнику с открытым ключом.
Пакеты ответа на маршрут - это просто последние элементы (nonce, открытый ключ, зашифрованные данные) пакета запроса данных на маршрут, скопированные в новый пакет и отправленные по назначению.
Для обработки пакетов onion announce, toxcore сначала получает пакет announce и расшифровывает его.
Toxcore генерирует ping_ids, беря 32-байтный sha-хэш текущего времени, несколько секретных байт, сгенерированных при создании экземпляра, текущее время, деленное на 20-секундный тайм-аут, открытый ключ запрашивающего и ip/порт источника, с которого был получен пакет. Поскольку ip/порт, с которого был получен пакет, указан в ping_id, пакеты анонса, отправляемые с ping id, должны быть отправлены по тому же пути, что и пакет, от которого мы получили ping_id, иначе анонс будет неудачным.
Причина 20-секундного таймаута в Toxcore заключается в том, что он дает разумное время (от 20 до 40 секунд) для того, чтобы пир объявил о себе, учитывая при этом все возможные задержки с некоторыми дополнительными секундами.
Toxcore генерирует 2 разных ping id, первый генерируется с текущим временем (разделенным на 20), а второй - с текущим временем + 20 (разделенным на 20). Затем эти два ping id сравниваются с ping id в полученных пакетах. Причина в том, что хранение каждого полученного ping id может быть дорогостоящим и сделать нас уязвимыми к DoS-атаке. Этот метод гарантирует, что другая сторона не может генерировать ping_ids и должна запрашивать их. Причина для 2 ping_ids в том, что мы хотим убедиться, что тайм-аут составляет не менее 20 секунд и не может быть равен 0.
Если один из двух ping_ids равен открытому ключу, используемому для шифрования пакета анонса (pk, которым пир объявляет себя), то открытый ключ и данные отката сохраняются в структуре данных, используемой для хранения анонсированных пиров. Если реализация имеет ограничение на количество записей, которые она может хранить, она должна хранить только те записи, которые находятся ближе всего (определяется функцией расстояния DHT) к ее открытому ключу DHT. Если запись уже есть, информация будет просто обновлена новой, а таймаут для этой записи будет сброшен.
Toxcore имеет тайм-аут в 300 секунд для записей анонса, после чего они удаляются, что достаточно долго, чтобы убедиться, что записи не истекают преждевременно, но недостаточно долго, чтобы пиры оставались анонсированными в течение длительного времени после отключения.
Затем Toxcore копирует 4 узла DHT, ближайших к искомому открытому ключу, в новый пакет (ответ).
Toxcore посмотрит, есть ли искомый открытый ключ в структуре данных. Если нет, то он скопирует первый сгенерированный ping_id (тот, который был сгенерирован в текущее время) в ответ, установит число is_stored в 0 и отправит пакет обратно.
Если открытый ключ находится в структуре данных, то будет проверено, равен ли открытый ключ, использованный для шифрования пакета announce, объявленному открытому ключу, если нет, то это означает, что пир ищет пир и что нам это известно. Это означает, что is_stored устанавливается в 1, и открытый ключ данных обратной отправки в записи анонса копируется в пакет.
Если он (ключ, используемый для шифрования пакета объявления) равен (объявленному открытому ключу, который также является "открытым ключом, который мы ищем" в пакете объявления), что означает, что пир объявляет о себе и для него существует запись, то открытый ключ данных, отправляемых обратно, проверяется на то, равен ли он тому, который был в пакете. Если он не равен, это означает, что он устарел, возможно, потому что экземпляр toxcore анонсирующего пира был перезапущен и поэтому его is_stored установлен в 0. Если он равен, это означает, что пир анонсирован правильно, поэтому is_stored установлен в 2. Затем первый сгенерированный ping_id копируется в пакет.
После создания пакета генерируется случайный 24-байтовый nonce, пакет шифруется (общий ключ, используемый для расшифровки запроса, может быть сохранен и использован для шифрования ответа, чтобы избежать дорогостоящей операции получения ключа), данные для отправки обратно копируются в незашифрованную часть, и пакет отправляется обратно как пакет ответа onion.
Для того чтобы объявить о себе с помощью пакетов onion announce, toxcore сначала берет пиров DHT, выбирает случайных и строит с ними луковые пути, сохраняя 3 узла, называя это путем, генерируя некоторые пары ключей для шифрования луковых пакетов и используя их для отправки луковых пакетов. Если пир подключен только по TCP, начальными узлами будут загрузочные узлы и подключенные TCP-реле (для первого пира в пути). Как только пир подключится к луку, он может пополнить свой список известных пиров за счет пиров, отправленных в ответах анонса, если это необходимо.
Луковые пути имеют различные таймауты в зависимости от того, является ли путь подтвержденным или неподтвержденным. Неподтвержденные пути (пути, от которых ядро никогда не получало ответов) имеют тайм-аут 4 секунды с 2 попытками, прежде чем они будут признаны нерабочими. Это связано с тем, что из-за состояния сети может существовать большое количество вновь созданных путей, которые не работают, и поэтому их многократные попытки приведут к тому, что поиск работающего пути займет гораздо больше времени. Таймаут для подтвержденного пути (от которого был получен ответ) составляет 10 секунд при 4 попытках без ответа. Максимальное время жизни подтвержденного пути составляет 1200 секунд, чтобы затруднить возможные атаки деанонимизации.
Toxcore сохраняет максимум 12 путей: 6 путей зарезервированы для объявления о себе, а 6 других используются для поиска друзей. Возможно, это не самый безопасный способ (некоторые узлы могут связать друзей вместе), однако он гораздо более производительный, чем использование разных путей для каждого друга. Главное преимущество заключается в том, что объявление и поиск осуществляются по разным путям, что затрудняет определение того, что пир с настоящим открытым ключом X дружит с Y и Z. Необходимы дополнительные исследования, чтобы найти лучший способ сделать это. Сначала в toxcore были разные пути для каждого друга, однако это означало, что каждый путь друга почти никогда не использовался (и не проверялся). При использовании небольшого количества путей для поиска требуется меньше ресурсов для поиска хороших путей. Используется 6 путей, потому что 4 было слишком мало и вызывало некоторые проблемы с производительностью, потому что в начале требовалось больше времени для поиска хороших путей, так как одновременно можно было попробовать только 4. Слишком большое число означает, что каждый путь используется (и тестируется) меньше. Причина, по которой число одинаково для обоих типов путей, заключается в упрощении кода.
Для поиска/объявления о себе пирам toxcore хранит 8 ближайших пиров для каждого ключа, по которому он ищет (или объявляет о себе). Чтобы заполнить их, он начинает с отправки запросов announce случайным пирам для всех открытых ключей, которые он ищет. Затем он рекурсивно ищет все более и более близких пиров (функция расстояния DHT), пока не найдет ни одного. Важно убедиться, что он не слишком агрессивен в поиске пиров, так как некоторые из них могут больше не быть онлайн, но пиры могут все еще посылать ответы announce со своей информацией. Toxcore хранит списки последних пингованных узлов для каждого искомого ключа, чтобы не пинговать мертвые узлы слишком агрессивно.
Toxcore решает, будет ли он посылать анонс-пакет одному из 4 пиров в анонс-ответе, проверяя, будет ли этот пир сохранен как один из 8 ближайших пиров, если он ответит; если нет, то он не посылает анонс-запрос, если да, то посылает.
Пиры помещаются в массив 8 ближайших пиров, только если они отвечают на запрос объявления. Если пир не отвечает на 3 запроса анонса, то считается, что его время истекло, и он удаляется.
Причина, по которой количество пиров равно 8, заключается в том, что меньшее число может сделать поиск и анонсирование слишком ненадежными, а большее число - слишком требовательным к пропускной способности/ресурсам.
Toxcore использует ping_array (см. ping_array) для 8-байтовых данных отсылки в пакетах announce для хранения информации, необходимой для обработки ответа (ключ для расшифровки, зачем он был отправлен? (чтобы объявить о себе или для поиска? Для какого ключа? и некоторая другая информация)). В целях безопасности он проверяет, что пакет был получен с правильного ip/порта и проверяет, является ли ключ в незашифрованной части пакета правильным открытым ключом.
Для пиров, которым мы объявляем о себе, если мы не объявлены, toxcore пытается каждые 3 секунды объявить о себе им, пока они не ответят, что мы объявили о себе, затем toxcore посылает пакет запроса объявления каждые 15 секунд, чтобы проверить, объявлены ли мы еще и повторно объявить о себе в то же время. Таймаут в 15 секунд означает, что ping_id, полученный в последнем пакете, не успеет истечь (минимальный таймаут 20 секунд), прежде чем он будет отправлен повторно через 15 секунд. Toxcore посылает каждый пакет announce с ping_id, ранее полученным от этого пира с тем же путем (если это возможно).
Для друзей все немного иначе. Важно начать поиск друзей после того, как мы полностью объявлены. Если предположить, что сеть идеальна, нам нужно будет выполнять поиск открытых ключей друзей только при первом запуске экземпляра (или при переходе в автономный режим и снова в автономный), так как пиры, запускающиеся после нас, смогут найти нас сразу же, просто выполнив поиск. Если мы начнем поиск друзей после объявления, то предотвратим сценарий, когда два друга одновременно запускают свои клиенты, но не могут сразу же найти друг друга, потому что начинают поиск друг друга, пока они еще не объявили себя.
По этой причине после того, как пир успешно объявлен в течение 17 секунд, пакеты объявления посылаются агрессивно каждые 3 секунды каждому известному близкому пиру (в списке из 8 пиров) для агрессивного поиска пиров, которые знают пира, которого мы ищем.
Существуют и другие способы, которые могут быть реализованы и которые все еще будут работать, но если вы создаете свою собственную реализацию, имейте в виду, что эти способы, скорее всего, не являются наиболее оптимизированными.
Если мы находим пиров (более 1), которые знают друга, мы отправляем им пакет данных onion с нашим открытым ключом DHT, до 2 TCP-реле, к которым мы подключены, и 2 близких нам пиров DHT, чтобы помочь другу подключиться к нам.
Луковые пакеты данных - это пакеты, отправляемые в качестве данных для маршрутизации пакетов.
Луковые пакеты данных:
| Length | Contents |
|---|---|
| 32 | Long term public key of sender |
| variable | Payload |
Полезная нагрузка шифруется долгосрочным закрытым ключом отправителя, долгосрочным открытым ключом получателя и nonce, используемым в пакете запроса данных для маршрутизации, который используется для отправки этого пакета данных onion (экономится 24 байта).
Пакет открытого ключа DHT:
| Length | Contents |
|---|---|
| 1 | uint8_t (0x9c) |
| 8 | uint64_t no_replay |
| 32 | Our DHT public key |
| [39, 204] | Maximum of 4 nodes in packed format |
Пакет будет принят, только если число no_replay больше числа no_replay в последнем полученном пакете.
Узлы, отправленные в этом пакете, имеют TCP, чтобы друг мог подключиться к нам.
Зачем еще один уровень шифрования? Нам нужно доказать получателю, что мы владеем долгосрочным открытым ключом, о котором мы говорим, когда отправляем ему наш открытый ключ DHT. Запросы друзей также отправляются с помощью пакетов данных onion, но их точный формат объясняется в Messenger.
Число no_replay - это защита на случай, если кто-то попытается воспроизвести старый пакет, и должно быть установлено на постоянно увеличивающееся число. Оно состоит из 8 байт, поэтому в качестве значения следует задать монотонное время высокого разрешения.
Мы отправляем этот пакет каждые 30 секунд, если есть более одного peer (из 8), который говорит, что наш друг объявлен на них. Этот пакет также может быть отправлен через модуль DHT как пакет запроса DHT (см. DHT), если мы знаем открытый ключ DHT друга и ищем его в DHT, но еще не соединились с ним. 30 секунд - это разумный тайм-аут, чтобы не переполнять сеть большим количеством пакетов и в то же время быть уверенным, что другой в конечном итоге получит пакет. Поскольку пакеты посылаются через каждый пир, который знает друга, повторная отправка сразу же без ожидания имеет высокую вероятность неудачи, так как вероятность потери пакетов со всеми (до 8) отправленными пакетами низка.
При отправке в качестве пакета запроса DHT (это данные, отправленные в пакете запроса DHT):
| Length | Contents |
|---|---|
| 1 | uint8_t (0x9c) |
| 32 | Long term public key of sender |
| 24 | Nonce |
| variable | Encrypted payload |
Полезная нагрузка шифруется долгосрочным закрытым ключом отправителя, долгосрочным открытым ключом получателя и ключом nonce, и содержит пакет открытых ключей DHT.
При отправке в качестве пакета запроса DHT пакет открытых ключей DHT (перед отправкой в качестве данных пакета запроса DHT) шифруется долгосрочными ключами отправителя и получателя и помещается в этот формат. Это делается по той же причине, что и двойное шифрование пакета данных onion.
Toxcore пытается повторно отправить этот пакет через DHT каждые 20 секунд. 20 секунд - это разумная скорость повторной отправки, которая не является слишком агрессивной.
В Toxcore есть обработчик пакетов запросов DHT, который передает полученные пакеты открытых ключей DHT из модуля DHT в этот модуль.
Если мы получаем пакет с открытым ключом DHT, мы сначала проверяем, является ли этот DHT-пакет от друга, если он не от друга, он будет отброшен. Затем будет проверен параметр no_replay, чтобы убедиться, что он хороший и во время сессии не было получено пакетов с меньшим значением. Ключ DHT, узлы TCP в упакованных узлах и узлы DHT в упакованных узлах будут переданы соответствующим модулям. Тот факт, что у нас есть открытый ключ DHT друга, означает, что этот модуль достиг своей цели.
Если друг находится в сети и подключен к нам, луковица остановит все свои действия для этого друга. Если пир перейдет в автономный режим, он возобновит поиск друга, как если бы toxcore был только что запущен.
Если toxcore выйдет из сети (трафик лука отсутствует в течение 20 секунд), toxcore агрессивно объявит о себе и начнет поиск друзей, как будто он только что был запущен.
12 Запросы друзей
Когда пользователь Tox добавляет кого-либо с помощью Tox, toxcore попытается отправить запрос на дружбу этому человеку. Запрос на дружбу содержит долгосрочный открытый ключ отправителя, номер nospam и сообщение.
Передача долгосрочного открытого ключа является основной целью запроса на дружбу, так как это то, что нужно коллеге, чтобы найти и установить соединение с отправителем. Долгосрочный открытый ключ - это то, что получатель добавляет в свой список друзей, если он принимает запрос на дружбу.
nospam - это номер, используемый для того, чтобы предотвратить спам в сети с действительными запросами друзей. Оно гарантирует, что только те люди, которые видели Tox ID пира могут отправить ему запрос на дружбу. nospam - это один из компонентов Tox ID.
nospam - это число или список чисел, заданный пиром. Только полученные запросы на дружбу, содержащие nospam, заданный пиром, отправляются на клиент для принятия или отклонения пользователем. nospam предотвращает отправку запросов на дружбу случайными пользователями в сети, не являющимися друзьями. nospam недостаточно длинный, чтобы быть безопасным, а значит, чрезвычайно устойчивый злоумышленник может умудриться послать кому-то спам-запрос на дружбу. 4 байта достаточно много для предотвращения спама от случайных пользователей в сети. nospam также может позволить пользователям Tox выдавать различные Tox ID и даже менять Tox ID, если кто-то найдет Tox ID и решит отправить ему сотни спам-запросов на дружбу. Изменение nospam'а остановило бы входящую волну спам-запросов друзей без каких-либо негативных последствий для списка друзей пользователя. Например, если бы пользователям пришлось изменить свой открытый ключ, чтобы предотвратить получение запросов на дружбу, это означало бы, что им пришлось бы отказаться от всех своих текущих друзей, поскольку друзья привязаны к открытому ключу. nospam не используется вообще, когда друзья добавлены друг к другу, поэтому его изменение не будет иметь никаких негативных последствий.
Запрос друга:
[uint32_t nospam][Сообщение (UTF8) 1 - ONION_CLIENT_MAX_DATA_SIZE байт].
Пакет запроса друга при отправке в виде пакета данных onion:
[uint8_t (32)][Запрос друга]
Пакет запроса друга при отправке в виде пакета данных net_crypto (Если мы напрямую связаны с peer из-за группового чата, но не являемся его друзьями):
[uint8_t (18)][Friend request].
Когда друг добавляется в toxcore с его Tox ID и сообщением, он добавляется в friend_connection, а затем toxcore пытается отправить запрос на дружбу.
При отправке запроса на дружбу toxcore будет проверять, не подключен ли уже тот, кому отправляется запрос на дружбу, используя соединение net_crypto, что может произойти, если оба находятся в одном групповом чате. Если это так, запрос на дружбу будет отправлен как пакет net_crypto, используя это соединение. Если нет, он будет отправлен как пакет данных onion.
Луковые пакеты данных содержат настоящий открытый ключ отправителя, и если соединение net_crypto установлено, это означает, что пир знает наш настоящий открытый ключ. Вот почему запрос на дружбу не обязательно должен содержать реальный открытый ключ отправителя.
Запросы на дружбу отправляются с экспоненциально возрастающим интервалом в 2 секунды, 4 секунды, 8 секунд и т.д. в toxcore. Это сделано для того, чтобы запросы на дружбу получали повторные отправки, но в конечном итоге они будут отправляться с такими большими интервалами, что, по сути, истекут. У отправителя нет возможности узнать, отклонил ли он запрос на дружбу, поэтому запросы на дружбу должны каким-то образом истекать. Обратите внимание, что интервал - это минимальный тайм-аут, если toxcore не может отправить запрос на дружбу, он будет пытаться снова, пока ему не удастся его отправить. Одной из причин невозможности отправить запрос друга может быть то, что луковица не нашла друга в луковице и поэтому не может отправить ему пакет данных.
Полученные запросы друзей передаются клиенту, клиент должен показать сообщение с запросом друга пользователю и спросить пользователя, хочет ли он принять запрос друга или нет. Запрос на дружбу принимается путем добавления в друзья пользователя, отправившего запрос на дружбу, и отклоняется путем простого игнорирования запроса.
Запросы на дружбу отправляются несколько раз, что означает, что для того, чтобы предотвратить отправку одного и того же запроса на дружбу клиенту несколько раз, toxcore хранит список последних реальных открытых ключей, от которых он получил запросы на дружбу, и отбрасывает все полученные запросы на дружбу от реального открытого ключа, который находится в этом списке. В toxcore этот список представляет собой простой круговой список. Есть много способов улучшить его и сделать более эффективным, так как круговой список не очень эффективен, но пока что он хорошо работает в toxcore.
Запросы друзей с открытых ключей, которые уже добавлены в список друзей, также должны отбрасываться.
13 Дружеское соединение
friend_connection - это модуль, который располагается поверх модулей DHT, Onion и net_crypto и обеспечивает связь между ними.
Друзья в friend_connection представлены их реальным открытым ключом. Когда друг добавляется в friend_connection, для него создается запись поиска в onion. Это означает, что модуль onion начнет искать этого друга и отправит ему свой открытый ключ DHT, а также TCP-реле, к которым он подключен, если соединение возможно только по TCP.
Как только onion возвращает открытый ключ DHT друга, открытый ключ DHT сохраняется, добавляется в список друзей DHT и создается новое соединение net_crypto. Любые TCP-реле, возвращенные луком для этого друга, передаются в соединение net_crypto.
Если DHT установит прямое UDP-соединение с другом, friend_connection передаст IP/порт друга в net_crypto, а также сохранит его, чтобы использовать для повторного соединения с другом в случае разрыва связи.
Если net_crypto обнаружит, что у друга другой открытый ключ DHT, что может произойти, если друг перезапустил свой клиент, net_crypto передаст новый открытый ключ DHT модулю onion и удалит запись DHT для старого открытого ключа DHT и заменит его новым. Текущее соединение net_crypto также будет уничтожено и будет создано новое с правильным открытым ключом DHT.
Когда соединение net_crypto для друга выходит в сеть, friend_connection сообщит модулю onion, что друг в сети, чтобы он мог прекратить тратить ресурсы на его поиск. Когда соединение с другом переходит в автономный режим, friend_connection сообщает об этом onion-модулю, чтобы он мог снова начать поиск друга.
Существует 2 типа пакетов данных, отправляемых друзьям с помощью соединения net_crypto, обрабатываемых на уровне friend_connection: пакеты Alive и пакеты TCP relay. Пакеты Alive - это пакеты, в которых идентификатор пакета или первый байт данных (единственный байт в этом пакете) равен 16. Они используются для того, чтобы проверить, находится ли другой друг все еще в сети. net_crypto не имеет таймаута при установлении соединения, поэтому таймаут отлавливается с помощью этого пакета. В toxcore этот пакет отправляется каждые 8 секунд. Если ни один из этих пакетов не получен в течение 32 секунд, соединение прерывается и завершается. Эти цифры, похоже, вызывают меньше всего проблем, а 32 секунды - не слишком большой срок, так что если друг отключится, toxcore не будет ошибочно считать, что он находится в сети слишком долго. Обычно, когда друг выходит из сети, он успевает послать пакет disconnect в соединении net_crypto, что делает его неактивным почти мгновенно.
Таймаут для прекращения повторных попыток соединения с другом путем создания новых net_crypto соединений, когда старое соединение разрывается, в toxcore такой же, как и таймаут для DHT пиров (122 секунды). Однако он рассчитывается от последнего времени получения открытого ключа DHT для друга или времени, когда соединение net_crypto друга перешло в оффлайн после того, как он был онлайн. Наибольшее время используется для расчета таймаута. net_crypto соединения будут воссоздаваться (если соединение не удалось) до этого таймаута.
friend_connection отправляет список из 3 ретрансляторов (столько же, сколько целевое количество TCP-релейных соединений в TCP_connections) каждому подключенному другу каждые 5 минут в toxcore. Непосредственно перед отправкой ретрансляторов они ассоциируются с текущим соединением net_crypto->TCP_connections. Это облегчает соединение двух друзей с помощью ретрансляторов, так как друг, получивший пакет, свяжет отправленный ретранслятор с соединением net_crypto, с которого он его получил. Когда обе стороны сделают это, они смогут соединиться друг с другом с помощью ретрансляторов. Идентификатор пакета или первый байт пакета share relay packets - 0x11. Затем следуют некоторые TCP-реле, хранящиеся в формате упакованного узла.
| Length | Contents |
|---|---|
| 1 | uint8_t (0x11) |
| variable | TCP relays in packed node format (see DHT) |
Если локальные IP получены как часть пакета, локальный IP будет заменен на IP аналога, отправившего ретрансляцию. Это происходит потому, что мы предполагаем, что это лучший способ попытаться подключиться к TCP-реле. Если пир, отправивший реле, использует локальный IP, то для подключения к реле следует использовать отправленный локальный IP.
Все остальные пакеты данных передаются friend_connection до верхнего модуля Messenger. Он также разделяет пакеты с потерями и без потерь с помощью net_crypto.
Friend connection заботится об установлении соединения с другом и предоставляет верхнему уровню Messenger простой интерфейс для получения и отправки сообщений, добавления и удаления друзей, а также для того, чтобы узнать, подключен ли друг (онлайн) или не подключен (офлайн).
14 Tox ID
Tox ID используется для идентификации сверстников, чтобы их можно было добавить в друзья в Tox. Чтобы добавить друга, пользователь Tox должен иметь его Tox ID. Tox ID содержит долгосрочный открытый ключ пира (32 байта), затем 4-байтовое значение nospam (см.: friend_requests) и 2-байтовую контрольную сумму XOR. Способ отправки Tox ID другим зависит от пользователя и клиента, но рекомендуемый способ - закодировать его в шестнадцатеричном формате и попросить пользователя вручную отправить его другу с помощью другой программы.
Tox ID:
Посмотреть вложение 54378
| Length | Contents |
|---|---|
| 32 | long term public key |
| 4 | nospam |
| 2 | checksum |
Контрольная сумма вычисляется путем XOR первого двух байтов ID со следующими двумя байтами, затем со следующими двумя байтами, пока все 36 байтов не будут XOR вместе. Затем результат добавляется в конец и образует Tox ID.
Пользователь должен убедиться, что Tox ID не будет перехвачен и заменен в пути на другой Tox ID, что означает, что друг подключится не к пользователю, а к злоумышленнику, хотя при принятии разумных мер предосторожности это выходит за рамки Tox. Tox предполагает, что пользователь убедился в том, что он использует правильный Tox ID, принадлежащий указанному лицу, для добавления друга.
15 Мессенджер
Messenger - это модуль, находящийся на вершине всех остальных модулей. Он находится на вершине friend_connection в иерархии toxcore.
Messenger заботится об отправке и получении сообщений, используя соединение, предоставляемое friend_connection. Модуль предоставляет возможность друзьям соединяться и делает его пригодным для использования в качестве мессенджера. Например, Messenger позволяет пользователям устанавливать псевдоним и сообщение о статусе, которые он затем передает друзьям, когда они находятся в сети. Он также позволяет пользователям отправлять сообщения друзьям и строит систему мгновенных сообщений поверх модуля
friend_connection нижнего уровня.
Messenger предлагает два способа добавления друга. Первый способ - добавить друга, используя только его долгосрочный открытый ключ. Этот способ используется, когда необходимо добавить друга, но по какой-то причине запрос на добавление не должен быть отправлен. Друга можно только добавить. Этот метод чаще всего используется для приема запросов на добавление в друзья, но может применяться и в других случаях. Если два друга добавят друг друга с помощью этой функции, они соединятся друг с другом. Добавление друга с помощью этого метода просто добавляет друга в friend_connection и создает для него новую запись в Messenger.
Второй способ добавления друга - это использование его Tox ID и сообщения, которое нужно отправить в запросе на добавление друга. При таком способе добавления друзей запрос на добавление в друзья с заданным сообщением будет отправлен тому собеседнику, чей Tox ID был добавлен. Метод похож на первый, за исключением того, что запрос на дружбу создается и отправляется другому пиру.
Когда соединение, связанное с другом Messenger, переходит в режим онлайн, ему отправляется пакет ONLINE. Друзья устанавливаются как онлайн только в том случае, если получен пакет ONLINE.
Как только друг переходит в режим онлайн, Messenger прекращает отправку запросов друзей этому другу, если он их отправлял, так как они являются избыточными для этого друга.
Друзья будут переведены в автономный режим, если либо соединение, связанное с ними, перейдет в автономный режим, либо если от друга будет получен пакет OFFLINE.
Пакеты Messenger отправляются другу, используя соединение с другом в режиме онлайн.
Если Messenger необходимо проверить, были ли получены другом какие-либо пакеты без потерь из следующего списка, например, для реализации квитанций для текстовых сообщений, можно использовать net_crypto. Номер пакета net_crypto, используемого для отправки пакетов, должен быть отмечен, а затем net_crypto проверит, находится ли нижняя часть массива отправки после этого номера пакета. Если это так, значит, друг их получил. Обратите внимание, что номера пакетов net_crypto могут переполниться через длительное время, поэтому проверка должна происходить в пределах 2**32 пакетов net_crypto, отправленных с одним и тем же соединением друга.
Получение сообщений для сообщений действий и обычных текстовых сообщений осуществляется путем добавления номера пакета net_crypto каждого сообщения вместе с номером получения в начало связанного списка, который есть у каждого друга по мере их отправки. Каждый цикл Messenger считывает записи снизу, удаляет записи и передает их клиенту, пока не будет достигнута запись, ссылающаяся на пакет, еще не полученный другом, когда это происходит, процесс останавливается.
Список пакетов Messenger:
15.1 ONLINE
длина: 1 байт
| Length | Contents |
|---|---|
| 1 | uint8_t (0x18) |
Отправляется другу при установлении соединения, чтобы он отметил нас в списке друзей как онлайн. Этот пакет и пакет OFFLINE необходимы, поскольку соединения friend_connections могут быть установлены с другими друзьями, не являющимися участниками группового чата. Эти два пакета используются для различения между этими друзьями, связанными с пользователем через групповые чаты, и настоящими друзьями, которые должны быть отмечены как онлайн в списке друзей.
При получении этого пакета Messenger покажет, что пользователь находится в сети.
15.2 OFFLIN
длина: 1 байт
| Length | Contents |
|---|---|
| 1 | uint8_t (0x19) |
Отправлено другу при удалении друга. Запрещает удаленному другу видеть нас в сети, если мы подключены к ним из-за группового чата.
Получив этот пакет, Messenger покажет этот одноранговый узел как не в сети.
15,3 NICKNAME
длина: от 1 байта до 129 байт.
| Длина | Содержание |
|---|---|
| 1 | uint8_t(0x30) |
| [0, 128] | Псевдоним в виде строки байтов UTF8 |
Используется для отправки псевдонима пира другим. Этот пакет должен быть отправлен каждый раз каждому другу каждый раз, когда они выходят в сеть и каждый раз никнейм изменен.
15,4 STATUSMESSAGE
длина: от 1 байта до 1008 байт.
| Длина | Содержание |
|---|---|
| 1 | uint8_t(0x31) |
| [0, 1007] | Сообщение о состоянии в виде строки байтов UTF8 |
Используется для отправки сообщения о состоянии однорангового узла другим. Этот пакет должен быть отправляется каждый раз каждому другу каждый раз, когда они выходят в сеть и каждый раз, когда сообщение о состоянии изменено.
15,5 USERSTATUS
длина: 2 байта
| Длина | Содержание |
|---|---|
| 1 | uint8_t(0x32) |
| 1 | uint8_tстатус (0 = онлайн, 1 = нет, 2 = занят) |
Используется для отправки статуса пользователя узла другим. Этот пакет должен быть отправлен каждый раз каждому другу каждый раз, когда они выходят в сеть и каждый раз, когда пользователь статус изменен.
15,6 TYPING
длина: 2 байта
| Длина | Содержание |
|---|---|
| 1 | uint8_t(0x33) |
| 1 | uint8_tстатус ввода (0 = не печатать, 1 = печатать) |
Используется, чтобы сообщить другу, печатает пользователь в данный момент или нет.
15,7 MESSAGE
| Длина | Содержание |
|---|---|
| 1 | uint8_t(0x40) |
| [0, 1372] | Сообщение в виде строки байтов UTF8 |
Используется для отправки обычного текстового сообщения другу.
15,8 ACTION
| Длина | Содержание |
|---|---|
| 1 | uint8_t(0x41) |
| [0, 1372] | Сообщение о действии в виде строки байтов UTF8 |
Используется для отправки сообщения действия (например, действия IRC) другу.
15,9 MSI
| Длина | Содержание |
|---|---|
| 1 | uint8_t(0x45) |
| ? | данные |
Зарезервировано для использования Tox AV.
15.10 Пакеты, связанные с передачей файлов
15.10.1 FILE_SENDREQUEST
| Length | Contents |
|---|---|
| 1 | uint8_t (0x50) |
| 1 | uint8_t file number |
| 4 | uint32_t file type |
| 8 | uint64_t file size |
| 32 | file id (32 bytes) |
| [0, 255] | filename as a UTF8 byte string |
Обратите внимание, что тип и размер файла передаются в формате с обратным порядком байтов/сетевым байтом.
15.10.2 FILE_CONTROL
длина: 4 байта, если тип_управления не seek. 8 байт, если тип управления - seek.
| Length | Contents |
|---|---|
| 1 | uint8_t (0x51) |
| 1 | uint8_t send_receive |
| 1 | uint8_t file number |
| 1 | uint8_t control_type |
| 8 | uint64_t seek parameter |
send_receive равен 0, если управление направлено на отправляемый файл (пиром, посылающим управление файлом), и 1, если оно направлено на получаемый файл.
control_type может быть одним из: 0 = принять, 1 = приостановить, 2 = убить, 3 = seek.
Параметр seek включен только в том случае, если control_type равен seek (3).
Обратите внимание, что если он включен, параметр seek будет отправлен в формате big endian/network byte.
15.10.3 FILE_DATA
длина: от 2 до 1373 байт.
| Length | Contents |
|---|---|
| 1 | uint8_t (0x52) |
| 1 | uint8_t file number |
| [0, 1371] | file data piece |
Файлы передаются в Tox с помощью передачи файлов.
Чтобы инициировать передачу файла, друг создает и отправляет пакет FILE_SENDREQUEST другу, которому он хочет инициировать передачу файла.
Первая часть пакета FILE_SENDREQUEST - это номер файла. Номер файла - это номер, используемый для идентификации данной передачи файла. Поскольку номер файла представлен числом в 1 байт, максимальное количество одновременно передаваемых файлов, которые Tox может отправить другу, составляет 256. 256 передач файлов на одного друга достаточно, чтобы клиенты могли использовать такие приемы, как постановка файлов в очередь, если требуется отправить больше файлов.
256 исходящих файлов на одного друга означает, что между двумя пользователями может быть максимум 512 одновременных передач файлов, если считать вместе входящие и исходящие передачи файлов.
Поскольку номера файлов используются для идентификации передачи файла, при создании новой исходящей передачи файла экземпляр Tox должен убедиться, что используется номер файла, который не используется для другой исходящей передачи файла тому же другу. Номера файлов выбираются отправителем файла и остаются неизменными в течение всего времени передачи файла. Номер файла используется пакетами FILE_CONTROL и FILE_DATA для идентификации того, для какой передачи файла предназначены эти пакеты.
Вторая часть запроса на передачу файла - это тип файла. Это просто число, которое идентифицирует тип файла. Например, tox.h определяет тип файла 0 как обычный файл, а тип 1 как аватар, что означает, что клиент Tox должен использовать этот файл как аватар. Тип файла никак не влияет на способ передачи файла или поведение передачи файла. Он устанавливается клиентом Tox, который создает передачу файлов и отправляет их другу нетронутыми.
Размер файла указывает на общий размер файла, который будет передан. Размер файла UINT64_MAX (максимальное значение в uint64_t) означает, что размер файла не определен или неизвестен. Например, если бы кто-то хотел использовать передачу файлов Tox для потоковой передачи данных, он бы установил размер файла равным UINT64_MAX. Размер файла 0 действителен и ведет себя точно так же, как обычная передача файла.
Идентификатор файла - это 32 байта, которые могут быть использованы для уникальной идентификации передачи файла. Например, при передаче аватаров он используется как хэш аватара, чтобы получатель мог проверить, есть ли у него уже такой аватар для друга, что экономит полосу пропускания. Он также используется для идентификации прерванных передач файлов при перезагрузках toxcore (подробнее см. раздел передачи файлов в tox.h). Реализации передачи файлов не важно, каков идентификатор файла, так как он используется только вышестоящими программами.
Последней частью передачи файла является необязательное имя файла, которое используется для того, чтобы сообщить получателю имя файла.
Когда получен пакет FILE_SENDREQUEST, реализация проверяет его и отправляет информацию клиенту Tox, который решает, принять ему передачу файла или нет.
Чтобы отказаться или отменить передачу файла, он посылает пакет FILE_CONTROL с control_type 2 (kill).
Пакеты FILE_CONTROL используются для управления передачей файлов. Пакеты FILE_CONTROL используются для приема/отмены, приостановки, отмены/завершения и поиска передачи файлов. Параметр control_type указывает, что делает пакет управления файлом.
Параметры send_receive и номер файла используются для идентификации конкретной передачи файла. Поскольку номера файлов для исходящих и входящих файлов не связаны друг с другом, параметр send_receive используется для определения того, принадлежит ли номер файла отправляемым или принимаемым файлам. Если send_receive равен 0, номер файла соответствует файлу, отправляемому пользователем, посылающим пакет управления файлами. Если send_receive равен 1, он соответствует файлу, получаемому пользователем, отправляющим пакет управления файлами.
control_type указывает цель пакета FILE_CONTROL. control_type 0 означает, что пакет FILE_CONTROL используется для того, чтобы сообщить другу, что передача файла принята или что мы отменяем приостановку ранее приостановленной (нами) передачи файла. control_type 1 используется для того, чтобы сообщить другой стороне о приостановке передачи файла.
Если одна сторона приостанавливает передачу файла, то именно она должна отменить паузу. Если обе стороны приостанавливают передачу файла, то перед возобновлением передачи файла обе стороны должны снять паузу. Например, если отправитель приостанавливает передачу файла, получатель не должен иметь возможности ее отменить. Чтобы отменить приостановку передачи файла, используется control_type 0. Файлы могут быть приостановлены только тогда, когда они находятся в процессе передачи и были приняты.
control_type 2 используется для уничтожения, отмены или отказа от передачи файла. Когда получен FILE_CONTROL, целевая передача файла считается мертвой, немедленно стирается, а ее номер файла может быть использован повторно. Пир, отправивший FILE_CONTROL, должен также стереть целевую передачу файла со своей стороны. Этот тип управления может быть использован обеими сторонами передачи в любое время.
control_type 3, тип управления seek используется для того, чтобы сообщить отправителю файла начать отправку с индекса, отличного от 0. Он может быть использован только сразу после получения пакета FILE_SENDREQUEST и до принятия файла путем отправки FILE_CONTROL с control_type 0. Когда используется этот тип управления, к FILE_CONTROL добавляется дополнительное 8-байтовое число в формате big endian, которое отсутствует в других типах управления. Это число указывает на индекс в байтах от начала файла, с которого отправитель должен начать отправку файла. Цель этого типа управления - гарантировать, что файлы могут быть возобновлены при перезагрузке ядра. Клиенты Tox могут узнать, получили ли они часть файла, используя идентификатор файла, а затем с помощью этого пакета сообщить другой стороне, чтобы она начала отправку с последнего полученного байта. Если позиция поиска больше или равна размеру файла, пакет поиска недействителен, и принимающая сторона отбрасывает его.
Чтобы принять файл, Tox посылает пакет seek, если он необходим, а затем посылает пакет FILE_CONTROL с control_type 0 (accept), чтобы сообщить отправителю файла, что файл был принят.
Как только передача файла будет принята, отправитель файла начнет посылать данные файла последовательными фрагментами с начала файла (или с позиции из пакета поиска FILE_CONTROL, если таковой был получен).
Файловые данные отправляются с помощью пакетов FILE_DATA. Номер файла соответствует передаче файла, к которому принадлежат куски файла. Получатель считает, что передача файла завершена, как только получен чанк с размером данных файла, не равным максимальному размеру (1371 байт). Таким образом отправитель сообщает получателю, что передача файла завершена при передаче файлов, когда размер файла неизвестен (установлен в UINT64_MAX). Приемник также считает, что если объем полученных данных равен размеру файла, полученному в FILE_SENDREQUEST, то отправка файла завершена и файл успешно получен. Сразу после этого приемник освобождает номер файла, чтобы новая входящая передача файла могла использовать этот номер файла. Реализация должна отбрасывать все полученные дополнительные данные, которые превышают размер файла, полученного в начале.
При передаче файлов размером 0 отправитель посылает один пакет FILE_DATA с размером данных файла 0.
Отправитель узнает, что получатель успешно принял файл, проверив, получил ли друг последний отправленный пакет FILE_DATA (содержащий последний фрагмент файла). Net_crypto можно использовать для проверки получения пакетов, отправленных через него, сохраняя номер отправленного пакета и проверяя позже в net_crypto, был ли он получен или нет. Как только net_crypto сообщает, что другой получил пакет, передача файла считается успешной, стертой, а номер файла может быть повторно использован для отправки новых файлов.
Пакеты FILE_DATA должны отправляться так быстро, как только соединение net_crypto может их обработать, соблюдая контроль перегрузки.
Если друг выходит из сети, все передачи файлов очищаются в toxcore. Это упрощает работу toxcore, так как ему не приходится иметь дело с возобновлением передачи файлов. Это также упрощает работу клиентов, поскольку метод возобновления передачи файлов остается неизменным, даже если клиент перезапускается или toxcore теряет соединение с другом из-за плохого интернет-соединения.
15.11 Пакеты, относящиеся к групповому чату
| Packet ID | Packet Name |
|---|---|
| 0x60 | INVITE_GROUPCHAT |
| 0x61 | ONLINE_PACKET |
| 0x62 | DIRECT_GROUPCHAT |
| 0x63 | MESSAGE_GROUPCHAT |
| 0xC7 | LOSSY_GROUPCHAT |
Messenger также заботится о сохранении списка друзей и другой информации о друзьях, чтобы можно было закрыть и запустить toxcore, сохранив всех друзей, долгосрочный ключ и информацию, необходимую для повторного подключения к сети.
Важная информация, которую хранит messenger, включает: долгосрочный закрытый ключ, текущее значение nospam, открытые ключи друзей и все запросы друзей, которые пользователь отправляет в данный момент. Узлы DHT сети, TCP-реле и некоторые луковые узлы хранятся для облегчения повторного подключения.
В дополнение к этому может храниться множество дополнительных данных, таких как имена друзей, наше текущее имя пользователя, сообщения о статусе друзей, наше сообщение о статусе и т.д... могут быть сохранены. Точный формат сохранения toxcore будет объяснен позже.
TCP-сервер запускается из модуля мессенджера toxcore, если клиент включил его. TCP-сервер обычно запускается самостоятельно, как часть пакета узла bootstrap, но он может быть включен в клиентах. Если он включен в toxcore, Messenger добавит запущенный TCP-сервер к TCP-реле.
Messenger - это модуль, преобразующий код, который может соединяться с друзьями на основе открытого ключа, в настоящий мессенджер.
16 Группа
Групповые чаты в Tox работают путем временного добавления некоторых пиров (до 4), присутствующих в групповом чате, в качестве временных друзей friend_connection, которые удаляются при выходе из группового чата.
Каждый пир в групповом чате идентифицируется своим реальным долгосрочным открытым ключом, однако пиры передают свои открытые ключи DHT друг другу через групповой чат, чтобы ускорить соединение, поскольку пирам не нужно искать открытые ключи DHT друг друга с помощью onion, что произошло бы, если бы они добавили себя в качестве обычных друзей.
Преимуществом использования friend_connection является то, что групповым чатам не нужно иметь дело с такими вещами, как hole-punching, пиры только на TCP или другие низкоуровневые сетевые вещи. Однако недостатком является то, что каждый пир знает реальный долгосрочный открытый ключ и открытый ключ DHT, что означает, что такие групповые чаты должны использоваться только между друзьями.
Чтобы соединиться друг с другом, два пира должны добавить друг друга в список друзей. Это не является проблемой, если в групповом чате равное или меньшее число участников, чем 5, так как каждый из 5 участников добавит 4 других в список друзей. При большем количестве участников должен быть способ гарантировать, что участники смогут соединиться с другими участниками группового чата.
Поскольку максимальное количество пользователей в групповом чате, с которыми можно установить дружеские связи, равно 4, если все пользователи в групповом чате расположены в виде идеального круга и каждый пользователь соединяется с двумя пользователями, которые находятся ближе всего справа от него и двумя пользователями, которые находятся ближе всего слева от него, пользователи должны образовать хорошо связанный круг пользователей.
Групповые чаты в toxcore делают это путем одновременной подстановки реального долгосрочного открытого ключа пира и всех остальных в группе (наш PK - PK другого пира) и нахождения двух пиров, для которых результат этой операции наименьший. Затем операция инвертируется (другой PK - наш PK), и эта операция повторяется со всеми открытыми ключами сверстников в группе. Выбираются 2 пира, для которых результат снова наименьший.
В результате получаются 4 пира, которые затем добавляются в качестве дружеского соединения и ассоциируются с группой. Если каждый пир в группе сделает это, они образуют круг идеально связанных пиров.
Когда одноранговые пользователи соединены друг с другом в круг, они передают друг другу сообщения. Каждый раз, когда пир покидает группу или присоединяется новый пир, каждый участник чата пересчитывает пиров, с которыми он должен соединиться.
Чтобы присоединиться к групповому чату, пользователь должен сначала получить приглашение от своего друга. Чтобы создать групповой чат, собеседник сначала создает групповой чат, а затем приглашает в него людей. После того как их друзья окажутся в групповом чате, они смогут пригласить в него других своих друзей и так далее.
Для создания группового чата сверстник генерирует случайный 32-байтовый идентификатор, который будет использоваться для уникальной идентификации этого группового чата. 32 байта достаточно для того, чтобы при случайной генерации с помощью безопасного генератора случайных чисел каждый созданный групповой чат имел свой идентификатор. Цель этого 32-байтового идентификатора состоит в том, чтобы у пользователей был способ идентифицировать каждый групповой чат, чтобы они не могли, например, дважды присоединиться к групповому чату.
Групповой чат также будет иметь беззнаковый тип размером 1 байт. Этот тип указывает, к какому типу относится групповой чат; в настоящее время существуют следующие типы:
0: текст 1: аудио
Текстовые групповые чаты - это только текст, а аудио указывает на то, что групповой чат поддерживает отправку аудио, а также текста.
Групповой чат также идентифицируется уникальным беззнаковым 2-байтовым целым числом, которое в toxcore соответствует индексу группового чата в массиве, в котором он хранится. Каждый групповой чат в текущем экземпляре должен иметь свой номер. Этот номер используется пирами groupchat, которые непосредственно подключены к нам, чтобы сообщить нам, какие пакеты предназначены для какого groupchat. Вот почему каждый пакет groupchat содержит номер groupchat как часть пакета. Размещение 32-байтового идентификатора группового чата в каждом пакете привело бы к значительному увеличению потерь пропускной способности, поэтому вместо него используются номера групповых чатов.
Использование номера группы в качестве индекса массива, используемого для хранения экземпляров groupchat, рекомендуется, поскольку такой доступ обычно наиболее эффективен и гарантирует, что каждый groupchat имеет уникальный номер группы.
При создании нового groupchat пир добавляет себя в качестве пира groupchat с номером пира 0 и собственным долгосрочным открытым ключом и открытым ключом DHT.
Пригласительные пакеты:
Пакет приглашения:
| Length | Contents |
|---|---|
| 1 | uint8_t (0x60) |
| 1 | uint8_t (0x00) |
| 2 | uint16_t group number |
| 33 | Group chat identifier |
Идентификатор группового чата состоит из 1-байтового типа и 32-байтового идентификатора, соединенных вместе.
Пакет ответа
| Length | Contents |
|---|---|
| 1 | uint8_t (0x60) |
| 1 | uint8_t (0x01) |
| 2 | uint16_t group number (local) |
| 2 | uint16_t group number to join |
| 33 | Group chat identifier |
Чтобы пригласить друга в групповой чат, ему отправляется пакет приглашения. Эти пакеты отправляются с помощью Messenger (если вы посмотрите на раздел идентификаторов пакетов Messenger, там указаны все идентификаторы пакетов группового чата). Обратите внимание, что все номера, как и все другие номера, отправляемые с помощью пакетов Tox, отправляются в формате big endian.
Номер группового чата - это, как объяснялось выше, номер, используемый для уникальной идентификации экземпляра группового чата от всех других экземпляров группового чата, которые есть у собеседника. Он передается в пакете приглашения, поскольку он необходим другу для отправки ответных пакетов, связанных с групповым чатом.
Далее следует 1-байтовый тип с 32-байтовым идентификатором группового чата.
Чтобы отклонить приглашение, друг, получивший его, просто проигнорирует и отбросит его.
Чтобы принять приглашение, друг создаст свой собственный экземпляр groupchat с 32-байтовым идентификатором groupchat и 1-байтовым типом, отправленным в запросе, и пошлет ответный пакет приглашения. Друг также добавит того, кто отправил приглашение, в качестве временного приглашенного соединения groupchat.
Первый номер группы в ответном пакете - это номер группы чата, который только что создал приглашенный друг. Второй номер группы - это номер группового чата, который был отправлен в запросе на приглашение. Далее следует 1-байтовый тип и 32-байтовый идентификатор группового чата, которые были отправлены в запросе на приглашение.
Когда одноранговый пользователь получает пакет ответа на приглашение, он проверяет, соответствует ли идентификатор группы, отправленный в ответ, идентификатору группового чата с номером группы, также отправленным в ответ. Если все в порядке, будет сгенерирован новый номер peer для peer, который отправил пакет с ответом на приглашение. Затем пир со сгенерированным номером пира, его долгосрочным открытым ключом и открытым ключом DHT будет добавлен в список пиров группового чата. Также будет отправлен новый пакет peer, чтобы сообщить всем участникам группового чата о новом peer. Пир также будет добавлен в качестве временного приглашенного соединения группового чата.
Номера сверстников используются для уникальной идентификации каждого сверстника в групповом чате. Они используются в пакетах сообщений группового чата, чтобы получающие их пользователи могли узнать, кто или какой пользователь группового чата их отправил. Поскольку пакеты группового чата передаются, они должны содержать что-то, по чему другие смогут определить отправителя. Поскольку размещение 32-байтного открытого ключа в каждом пакете было бы расточительным, вместо него используется 2-байтный номер сверстника. Каждый участник группового чата имеет уникальный номер. Toxcore генерирует каждый пиринговый номер случайным образом, но следит за тем, чтобы новые генерируемые пиринговые номера не были равны текущим, уже используемым другими пирингами в групповом чате. Если два пользователя присоединяются к групповому чату с двух разных конечных точек, существует небольшая вероятность того, что обоим будет присвоен один и тот же номер, однако на практике эта вероятность достаточно мала и не является проблемой.
Временные приглашенные подключения к групповому чату - это подключения к пригласителю группового чата, используемые сверстниками группового чата для загрузки в групповой чат. Это то же самое, что и соединения со сверстниками через дружеские соединения, за исключением того, что они отбрасываются после того, как сверстник полностью подключен к групповому чату.
Пакет Peer Online:
| Length | Contents |
|---|---|
| 1 | uint8_t (0x61) |
| 2 | uint16_t group number (local) |
| 33 | Group chat identifier |
Пакет Peer покинул канал:
| Length | Contents |
|---|---|
| 1 | uint8_t (0x62) |
| 2 | uint16_t group number (local) |
| 1 | uint8_t (0x01) |
Чтобы соединение группового чата работало, оба участника группового чата должны пытаться напрямую соединиться друг с другом.
Соединения группового чата устанавливаются, когда оба собеседника, желающие соединиться друг с другом, либо создают новое дружеское соединение для соединения друг с другом, либо повторно используют существующее дружеское соединение, которое соединяет их вместе (если они друзья или уже соединены вместе из-за другого группового чата).
Как только соединение с другим сверстником будет открыто, ему будет отправлен пакет peer online. Цель онлайн-пакета - сообщить сверстнику, что мы хотим установить с ним соединение группового чата, и сообщить ему номер нашего экземпляра группового чата. Пакет peer online содержит номер группы, тип группы и 32-байтовый идентификатор группового чата. Номер группы - это номер группы, который одноранговый пользователь имеет для группы с идентификатором группы, отправленным в пакете.
Когда обе стороны отправляют пакет online другому peer, устанавливается соединение.
Когда онлайн-пакет получен, номер группы для связи с ней сохраняется. Если соединение со сверстником уже установлено (уже получен пакет online), то пакет отбрасывается. Если групповое соединение с этим сверстником не установлено, пакет отбрасывается. Если это первое групповое соединение с данной группой, которое мы устанавливаем, посылается пакет запроса peer. Это делается для того, чтобы мы могли получить список пиров из группы.
Пакет peer leave отправляется peer непосредственно перед уничтожением группового соединения. Он используется только для того, чтобы сообщить другой стороне, что соединение мертво, если соединение с другом используется не только для группового чата (другой групповой чат, соединение с другом). В противном случае, другой пир увидит, что соединение с другом перешло в автономный режим, что побудит его прекратить его использование и убить связанное с ним групповое соединение.
Пакет запроса от пира:
| Length | Contents |
|---|---|
| 1 | uint8_t (0x62) |
| 2 | uint16_t group number |
| 1 | uint8_t (0x08) |
Peer response packet:
| Length | Contents |
|---|---|
| 1 | uint8_t (0x62) |
| 2 | uint16_t group number |
| 1 | uint8_t (0x09) |
| variable | Repeated times number of peers: Peer info |
The Peer info structure is as follows:
| Length | Contents |
|---|---|
| 2 | uint16_t peer number |
| 32 | Long term public key |
| 32 | DHT public key |
| 1 | uint8_t Name length |
| [0, 255] | Name |
Title response packet:
| Length | Contents |
|---|---|
| 1 | uint8_t (0x62) |
| 2 | uint16_t group number |
| 1 | uint8_t (0x0a) |
| variable | Title |
Message packets:
| Length | Contents |
|---|---|
| 1 | uint8_t (0x63) |
| 2 | uint16_t group number |
| 2 | uint16_t peer number |
| 4 | uint32_t message number |
| 1 | uint8_t with a value representing id of message |
| variable | Data |
Lossy Message packets:
| Length | Contents |
|---|---|
| 1 | uint8_t (0xc7) |
| 2 | uint16_t group number |
| 2 | uint16_t peer number |
| 4 | uint16_t message number |
| 1 | uint8_t with a value representing id of message |
| variable |
Если получен пакет запроса от собеседника, получатель берет свой список собеседников и создает пакет ответа от собеседника, который затем отправляется другому собеседнику. Если в групповом чате слишком много собеседников, и пакет ответа собеседника будет больше, чем максимальный размер пакетов дружеского соединения (1373 байта), в ответ отправляется более одного пакета ответа собеседника. Пакет ответа Title также отправляется обратно. Таким образом пир, присоединившийся к групповому чату, узнает список пиров в групповом чате и название группового чата сразу после присоединения.
Пакеты ответа пиров просты и содержат информацию о каждом пире (номер пира, реальный открытый ключ, открытый ключ DHT, имя), приложенную друг к другу. Ответ на заголовок также прост.
Максимальная длина имен пиров группового чата и названия группового чата составляет 128 байт. Это такая же максимальная длина, как и у имен во всем toxcore.
Когда пир получает пакет(ы) ответа пира, он добавляет каждого из полученных пиров в свой список пиров groupchat, находит 4 ближайших к нему пира и создает с ними соединения groupchat, как было описано ранее.
Чтобы найти свой номер сверстника, сверстник найдет себя в списке полученных сверстников и использует присвоенный ему номер сверстника как свой собственный.
Пакеты сообщений используются для отправки сообщений всем коллегам в групповом чате. Чтобы отправить пакет сообщений, сверстник сначала берет свой номер сверстника и сообщение, которое он хочет отправить. Каждый отправленный пакет сообщений будет иметь номер сообщения, равный номеру последнего отправленного сообщения + 1. Как и все остальные номера (номер группового чата, номер peer) в пакете, номер сообщения в пакете будет в формате big endian. Когда пакет сообщений получен, принимающий его пир возьмет номер сообщения в пакете и посмотрит, больше ли он, чем тот, который он сохранил для пира с номером пира. Если это первый пакет Message, полученный для данного peer, то эта проверка опускается. Номер сообщения используется для того, чтобы узнать, был ли уже получен и передан пакет сообщения, чтобы предотвратить зацикливание пакетов в групповом чате. Если проверка номера сообщения говорит, что пакет уже был получен, то пакет отбрасывается. Если он еще не был получен, то пакет Message с сообщением отправляется (ретранслируется) всем текущим групповым соединениям (обычные групповые соединения + временные приглашенные групповые соединения), кроме того, от которого он был получен. Единственное, что должно измениться в пакете сообщения при его передаче - это номер группы.
16.1 Идентификаторы сообщений
16.1.1 ping (0x00)
Отправляется примерно каждые 60 секунд каждым пиром. Не содержит данных.
16.1.2 new_peer (0x10)
Добавление нового пира в чат
| Length | Contents |
|---|---|
| 2 | uint16_t Peer number |
| 32 | Long term public key |
| 32 | DHT public key |
16.1.3 kill_peer (0x11)
| Length | Contents |
|---|---|
| 2 | uint16_t Peer number |
16.1.4 Смена имени (0x30)
| Length | Contents |
|---|---|
| variable | Name (namelen) |
16.1.5 Иззменение названия чата (0x31)
| Length | Contents |
|---|---|
| variable | Title (titlelen) |
16.1.6 Собщение чата (0x40)
| Length | Contents |
|---|---|
| variable | Message (messagelen) |
16.1.7 Action (/me) (0x41)
| Length | Contents |
|---|---|
| variable | Message (messagelen) |
Ping-сообщения должны отправляться каждые 60 секунд каждым peer. Так другие участники узнают, что они все еще активны.
Когда присоединяется новый участник, участник, который пригласил присоединившегося участника, посылает сообщение о новом участнике, чтобы предупредить всех, что в чате появился новый участник. Когда получено сообщение о новом пире, пир в этом пакете должен быть добавлен в список пиров.
Сообщение Kill peer используется для того, чтобы сообщить, что пользователь покинул групповой чат. Оно отправляется тем, кто покидает групповой чат, непосредственно перед выходом из него.
Сообщения об изменении имени используются для изменения или установки имени отправляющего их пира. Они также посылаются присоединяющимся пиром сразу после получения списка пиров, чтобы сообщить другим свое имя.
Пакеты изменения названия используются для изменения названия группового чата и могут быть отправлены любым участником группового чата.
Сообщения чата и действия используются сверстниками группового чата для отправки сообщений другим участникам группового чата.
Пакеты сообщений с потерями используются для отправки аудиопакетов другим участникам группового аудиочата. Пакеты с потерями работают так же, как и обычные сообщения группового чата: они передаются всем участникам группового чата, пока все их не получат.
Некоторые различия между ними заключаются в том, что, во-первых, номер сообщения - это целое число в 2 байта. Если бы я хотел улучшить протокол групповых чатов, я бы сделал номер сообщения для обычных пакетов сообщений двухбайтовым. 1 байт означает, что одновременно может быть получено только 256 пакетов. С задержками в groupchats и 256 пакетами, соответствующими менее чем одному кадру видео высокого качества, это не будет работать. Вот почему было выбрано 2 байта.
Обратите внимание, что номер сообщения, как и все остальные номера в пакете, имеет формат big endian.
При получении пакета с потерями одноранговый компьютер сначала проверяет, не был ли он уже получен. Если нет, то пакет будет добавлен в список полученных пакетов, затем он будет передан своему обработчику, а затем отправлен двум ближайшим к отправителю пользователям groupchat, не являющимся отправителями. Причина, по которой для обычных пакетов сообщений используется 2, а не 4 (ну, 3, если мы не являемся отправителем), заключается в том, что это снижает использование полосы пропускания без снижения качества получаемого аудиопотока за счет пакетов с потерями. Пакеты сообщений также отправляются относительно редко, так что изменение этого параметра на 2 будет иметь минимальное влияние на использование полосы пропускания.
Чтобы проверить, был ли получен пакет, сохраняются последние до 65536 номеров полученных пакетов, текущие группы хранят последние 256 номеров пакетов, но это потому, что в настоящее время это только аудио. Если будет добавлено видео, то есть будет отправляться гораздо большее количество пакетов, это число будет увеличено. Если номер пакета находится в этом списке, значит он был получен.
Именно так работают групповые чаты в Tox.
17 network
Модуль network - это самый нижний по уровню в toxcore, от которого зависит все остальное. Этот модуль, по сути, является оберткой UDP сокета, служит сортировочной площадкой для пакетов, полученных сокетом, инициализирует и деинициализирует сокет. Он также содержит множество функций, связанных с сокетами, сетями и некоторые другие функции, например, функцию монотонного времени, используемую другими модулями toxcore.
В этом модуле следует отметить определение максимального размера UDP-пакета (MAX_UDP_PACKET_SIZE), который устанавливает максимальный размер UDP-пакета, который может отправлять и получать toxcore. Список всех идентификаторов UDP пакетов: NET_PACKET_*. Идентификаторы UDP-пакетов - это значение первого байта каждого UDP-пакета, и именно так каждый пакет сортируется в нужный модуль, который может его обработать. networking_registerhandler() используется модулями более высокого уровня для того, чтобы сообщить сетевому объекту, какие пакеты отправлять в какой модуль через обратный вызов.
Она также содержит структуры данных, используемые для ip-адресов в toxcore. IP4 и IP6 - это структуры данных для ipv4 и ipv6 адресов, IP - это структура данных для хранения любого из них (семейство может быть установлено в AF_INET (ipv4) или AF_INET6 (ipv6). Оно может быть установлено в другое значение, например TCP_ONION_FAMILY, TCP_INET, TCP_INET6 или TCP_FAMILY, которые являются недопустимыми значениями в сетевых модулях, но допустимыми значениями в некоторых других модулях и обозначают специальный тип ip), а IP_Port хранит структуру данных IP с портом.
Поскольку сетевой модуль взаимодействует непосредственно с базовой операционной системой с помощью своих функций сокетов, он имеет код для работы на windows, linux и т.д... в отличие от большинства модулей, которые находятся на более высоком уровне.
В настоящее время сетевой модуль использует метод опроса для чтения из UDP сокета. Функция networking_poll() вызывается для чтения всех пакетов из сокета и передачи их в обратные вызовы, установленные с помощью функции networking_registerhandler(). Причина, по которой используется опрос, просто в том, что так было проще написать, другой метод здесь был бы лучше.
Цель этого модуля - предоставить простой интерфейс к UDP сокету и другим функциям, связанным с сетью.
18 Массив Ping
Ping array - это массив, используемый в toxcore для хранения данных для пингов. Он позволяет хранить произвольные данные, которые можно извлечь позже, передав 8-байтовый идентификатор пинга, который был возвращен при сохранении данных. Он также освобождает данные из пингов, которые старше, чем задержка истечения срока действия пинга, установленная при инициализации массива.
Массивы пингов инициализируются параметрами size и timeout. Параметр size обозначает максимальное количество записей в массиве, а timeout - количество секунд для хранения записи в массиве. Таймаут и размер должны быть больше 0.
При добавлении записи в массив ping он возвращает 8-байтовое число, которое может быть использовано в качестве номера ping-пакета. Это число генерируется путем генерации случайного 8-байтового числа (toxcore использует криптографический безопасный генератор случайных чисел), деления его на общий размер массива и добавления индекса добавленного элемента. В результате генерируется случайное число, которое возвращает индекс элемента, добавленного в массив. Это число также сохраняется вместе с добавленными данными и текущим временем (для проверки тайм-аута). Данные добавляются в массив циклически (0, 1, 2, 3... (размер массива - 1), 0, 1, ...). Если массив переполнен, самый старый элемент перезаписывается.
Чтобы получить данные из массива ping, номер ping передается в функцию для получения данных из массива. По модулю числа ping с общим размером массива будет возвращен индекс, в котором находятся данные. Если по этому индексу нет данных, функция возвращает ошибку. Затем номер пинга сверяется с номером пинга, сохраненным для данного элемента, если они не равны, функция возвращает ошибку. Если элемент массива вышел по таймеру, функция возвращает ошибку. Если все проверки прошли успешно, функция возвращает именно те данные, которые были сохранены, и удаляет их из массива.
Массив Ping используется во многих местах в toxcore для эффективного отслеживания отправленных пакетов.
19 Формат состояния
Эталонная реализация Tox использует собственный двоичный формат для сохранения состояния клиента Tox между перезапусками. Этот формат далек от совершенства и со временем будет заменен. Для поддержания совместимости в будущем, он документирован здесь.
Двоичное кодирование всех целочисленных типов в формате состояния представляет собой последовательность байтов фиксированной ширины с кодировкой целого числа в Little Endian, если не указано иное.
| Length | Contents |
|---|---|
| 4 | Zeroes |
| 4 | uint32_t (0x15ED1B1F) |
| ? | List of sections |
19.1 Разделы
Ядро формата состояния состоит из списка секций. В начале каждого раздела указывается его тип и длина. В некоторых случаях раздел содержит только один элемент и поэтому занимает всю длину раздела. Это обозначается символом '?'.
| Length | Contents |
|---|---|
| 4 | uint32_t Length of this section |
| 2 | uint16_t Section type |
| 2 | uint16_t (0x01CE) |
| ? | Section |
Типы разделов:
| Name | Value |
|---|---|
| NospamKeys | 0x01 |
| DHT | 0x02 |
| Friends | 0x03 |
| Name | 0x04 |
| StatusMessage | 0x05 |
| Status | 0x06 |
| TcpRelays | 0x0A |
| PathNodes | 0x0B |
| EOF | 0xFF |
Не каждый раздел, перечисленный выше, требуется для восстановления из файла состояния. Требуется только NospamKeys.
19.1.1 Nospam и ключи (0x01)
| Length | Contents |
|---|---|
| 4 | uint32_t Nospam |
| 32 | Long term public key |
| 32 | Long term secret key |
19.1.2 DHT (0x02)
Этот раздел содержит список разделов, связанных с DHT.
| Длина | Содержание |
|---|---|
| 4 | uint32_t(0x159000D) |
| ? | Список разделов DHT |
19.1.2.1 Разделы DHT
Каждый раздел DHT имеет следующую структуру:
| Длина | Содержание |
|---|---|
| 4 | uint32_tДлина этого раздела |
| 2 | uint16_tТип секции DHT |
| 2 | uint16_t(0x11СЕ) |
| ? | Раздел DHT |
Типы разделов DHT:
| Имя | Ценить |
|---|---|
| Узлы | 0x04 |
19.1.2.1.1 Узлы (0x04)
Этот раздел содержит список узлов. Эти узлы используются для быстрого повторно подключиться к DHT после перезапуска клиента Tox.
| Длина | Содержание |
|---|---|
| ? | Список узлов |
Структура узла такая же, как Node Info. Примечание: это означает, что целые числа, хранящиеся в этих узлах, также хранятся в Big Endian.
19.1.3 Друзья (0x03)
В этом разделе находится список друзей. Друг может быть либо сверстником, с которым мы отправили запрос на добавление в друзья или коллеге, от которого мы приняли запрос на добавление в друзья.
| Length | Contents |
|---|---|
| 1 | uint8_t Status |
| 32 | Long term public key |
| 1024 | Friend request message as a byte string |
| 1 | PADDING |
| 2 | uint16_t Size of the friend request message (BE) |
| 128 | Name as a byte string |
| 2 | uint16_t Size of the name (BE) |
| 1007 | Status message as a byte string |
| 1 | PADDING |
| 2 | uint16_t Size of the status message (BE) |
| 1 | uint8_t User status (see also: USERSTATUS) |
| 3 | PADDING |
| 4 | uint32_t Nospam (only used for sending a friend request) |
| 8 | uint64_t Last seen time |
Статусы друзей:
| Status | Meaning |
|---|---|
| 0 | Not a friend |
| 1 | Friend added |
| 2 | Friend request sent |
| 3 | Confirmed friend |
| 4 | Friend online |
19.1.4 Имя (0x04)
| Длина | Содержание |
|---|---|
| ? | Имя в виде строки в кодировке UTF-8 |
19.1.5 Сообщение о состоянии (0x05)
| Длина | Содержание |
|---|---|
| ? | Сообщение о состоянии в виде строки в кодировке UTF-8 |
19.1.6 Статус (0x06)
| Длина | Содержание |
|---|---|
| 1 | uint8_tСтатус пользователя (см. также: USERSTATUS) |
19.1.7 TCP-реле (0x0A)
Этот раздел содержит список реле TCP.
| Длина | Содержание |
|---|---|
| ? | Список реле TCP |
19.1.8 Узлы пути (0x0B)
Этот раздел содержит список узлов пути, используемых для луковой маршрутизации.
| Длина | Содержание |
|---|---|
| ? | Список узлов пути |
19.1.9 EOF (0xFF)
Этот раздел указывает на конец файла состояния. В этом разделе нет содержание и, следовательно, его длина равна 0.
20 Тестирование
Заключительной частью архитектуры является протокол тестирования. Мы используем MessagePack Протокол RPC на основе для раскрытия языка независимые интерфейсы к внутренним функциям. Использование тестирования на основе свойств с случайные входные данные, а также специальные тесты пограничного случая помогают гарантировать, что реализация протокола Tox в соответствии с архитектурой, указанной в этом документ правильный.