Служебные сообщения о сообщениях

Подтверждение получения

Получение практически всех сообщений (за исключением некоторых чисто служебных, а также незашифрованных сообщений, используемых впротоколе создания авторизационного ключа) должно быть подтверждено. Для этого используется следующее служебное сообщение (не требующее подтверждения):

msgs_ack#62d6b459 msg_ids:Vector long = MsgsAck;

Сервер обычно подтверждает получение сообщения от клиента (как правило — RPC-запроса) с помощью RPC-ответа. Если же ответа долго нет, сервер может сначала отправить подтверждение получения, а через какое-то время — сам RPC-ответ.

Клиент обычно подтверждает получение сообщения от сервера (обычно — RPC-ответа), добавляя подтверждение в следующий RPC-запрос, если он отправляется не слишком поздно (скажем, если он возникает в течение 60-120 секунд после получения сообщения от сервера). Однако если долго нет повода послать сообщения к серверу, или если неподтвержденных сообщений от сервера становится много (скажем, больше 16), то клиент отправляет подтверждение само по себе.

Уведомление проигнорированном ошибочном сообщении

В некоторых случаях сервер может уведомить клиента, что присланное им сообщение было проигнорировано по той или иной причине. Отметим, что такое уведомление не может быть сгенерировано, если сообщение не было корректно расшифровано сервером.

bad_msg_notification#a7eff811
    bad_msg_id:long
    bad_msg_seqno:int
    error_code:int
= BadMsgNotification;

bad_server_salt#edab447b
    bad_msg_id:long
    bad_msg_seqno:int
    error_code:int
    new_server_salt:long
= BadMsgNotification;

Здесь error_code может принимать в том числе следующие значения:

  • 16 — слишком маленький msg_id (скорее всего, на клиенте неправильное время; имеет смысл синхронизировать его, использовав msg_id уведомления, и перепослать исходное сообщение с «правильным» msg_id, либо обернуть его в контейнер с новым msg_id, если исходное сообщение слишком долго ждало отправки на клиенте)
  • 17 — слишком большой msg_id (аналогично предыдущему случаю: надо синхронизировать время на клиенте и перепослать сообщение с правильным msg_id)
  • 18 — неправильные младшие два бита msg_id (сервер ожидает, что msg_id клиентских сообщений делится на 4)
  • 19msg_id контейнера совпал с msg_id ранее полученного сообщения (такого никогда не должно быть)
  • 20 — сообщение слишком старое, и невозможно проверить, получал ли сервер сообщение с таким msg_id или нет
  • 32 — слишком маленький msg_seqno (сервером уже было принято сообщение с меньшим msg_id, но с большим seqno, либо с таким же нечетным)
  • 33 — слишком большой msg_seqno (аналогично: есть сообщение с большим msg_id, но с меньшим seqno, либо с таким же нечетным)
  • 34 — ожидали четный msg_seqno (несущественное сообщение), получили нечетный
  • 35 — ожидали нечетный msg_seqno (существенное сообщение), получили четный
  • 48 — неправильная серверная соль (в этом случае присылается ответ bad_server_salt с правильной солью, надо перепослать сообщение с ней)
  • 64 — неправильный контейнер.

По замыслу, значения error_code группируются по (error_code 4): например, коды 0x40..0x4f соответствуют ошибкам при разборе контейнера.

Уведомления о проигнорированном сообщении не нуждаются в подтверждении (т.е. являются несущественными).

Важно: если на сервере изменился server_salt, или если у клиента неправильное время, на любой запрос будет получено уведомление указанного выше вида. Клиент должен проверить, что он действительно недавно отправлял сообщение с указанным msg_id, и если это так — обновить у себя поправку времени (разницу между серверными и клиентскими часами) и серверную соль, исходя из msg_id и server_salt уведомления, чтобы использовать их для (пере)отправки будущих сообщений. При этом исходное сообщение (то, на которое вернули уведомление об ошибке) тоже должно быть перепослано с более адекватным msg_id и/или server_salt.

Кроме того, клиент может обновлять значение server_salt, используемое при отправке сообщений на сервер, исходя из значений в rpc-ответах или контейнерах, содержащих rpc-ответ, при условии, что этот rpc-ответ действительно соответствует недавно отправленному запросу. (В случае сомнения лучше не обновлять, т.к. есть риск replay-атаки.)

Запрос информации о состоянии сообщений

Если одна из сторон долго не получает информации о состоянии отправленных ей сообщений, она может явно запросить ее у противоположной стороны:

msgs_state_req#da69fb52 msg_ids:Vector long = MsgsStateReq;

Ответ на этот запрос содержит в себе следующую информацию:

Сообщение информации о состоянии сообщений

msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;

Здесь req_msg_id — идентификатор запроса msgs_state_req, info — строка, которая для каждого сообщения из присланного списка msg_ids содержит ровно один байт с состоянием сообщения:

  • 1 = о сообщении ничего не известно (msg_id слишком мал, противоположная сторона могла его забыть)
  • 2 = сообщение не было получено (msg_id в диапазоне хранимых идентификаторов, однако противоположная сторона такого сообщения точно не получала)
  • 3 = сообщение не было получено (msg_id слишком велик, противоположная сторона его точно еще не получила)
  • 4 = сообщение было получено (отметим, что такой ответ заодно является подтверждением получения)
  • +8 = на сообщение уже было отправлено подтверждение
  • +16 = на сообщение не требуется подтверждение
  • +32 = идет или уже завершена обработка RPC-запроса, содержащегося в сообщении
  • +64 = на сообщение уже был сгенерирован содержательный ответ
  • +128 = другая сторона точно знает о том, что это сообщение было получено

Этот ответ не нуждается в подтверждении. Сам по себе он является подтверждением на соответствующий msgs_state_req.

Отметим, что если вдруг выясняется, что у противоположной стороны нет сообщения, которое вроде бы было ей отправлено, можно просто отправить это сообщение снова. Даже если на противоположную сторону вдруг придет сразу два экземпляра этого сообщения, дубль будет проигнорирован. (Если прошло слишком много времени и исходный msg_id уже не годится, надо обернуть сообщение в msg_copy.)

Добровольное информирование о состоянии сообщений

Любая сторона может добровольно информировать другую сторону о состоянии сообщений, посланных другой стороной.

msgs_all_info#8cc0d131 msg_ids:Vector long info:string = MsgsAllInfo

Перечисляются все коды сообщений, известных данной стороне, за исключением тех, для которых установлен флаг +128 или +16. Однако если установлен флаг +32, но не +64, то состояние сообщения все-таки будет прислано.

Такое сообщение не нуждается в подтверждении.

Расширенное добровольное информирование о состоянии одного сообщения

Обычно используется сервером для ответа на повторное получение сообщения msg_id, особенно в том случае, если на это сообщение был сгенерирован ответ, и этот ответ большой. Если ответ небольшой, сервер может вместо этого повторить сам ответ.

msg_detailed_info#276d3ec6 msg_id:long answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDetailedInfo;

Второй вариант используется для уведомления о сообщениях, которые были созданы на сервере не в результате rpc-запроса (например, уведомления о новых сообщениях), были отосланы клиенту какое-то время назад, но на них не было получено подтверждения.

Такое сообщение не нуждается в подтверждении.

Явный запрос перепосылки сообщений

msg_resend_req#7d861a08 msg_ids:Vector long = MsgResendReq;

В качестве ответа удаленная сторона немедленно перепосылает запрошенные сообщения, обычно по тому же соединению, по которому пришел этот запрос. Если сообщение с запрошенным msg_id отсутствует или уже было забыто, либо если оно было отправлено запрашивающей стороной (что понятно по четности), для таких msg_id присылается ответ MsgsStateInfo.