Skip to content

ParserStream::parse(Stream*, length) не обрабатывает частичное чтение Stream #52

Description

@igor-gia

ParserStream::parse(Stream*, length) не обрабатывает частичное чтение Stream

Описание проблемы

В GSON/src/utils/ParserStream.h метод

bool parse(Stream* stream, size_t length)

предполагает, что вызов

stream->readBytes(json, length)

обязательно вернёт сразу все length байт.

На ESP32-S3 с сетевым Stream это предположение не всегда выполняется. readBytes() может вернуть только часть запрошенных данных, хотя соединение остаётся рабочим и остальные данные поступят позднее.

В результате ParserStream считает это ошибкой и полностью сбрасывает JSON.

Окружение

  • МК: ESP32-S3 N16R8
  • Arduino IDE
  • ESP32 Arduino Core: 3.3.12
  • Источник данных: WiFiClientSecure
  • GSON: текущая версия из Arduino Library Manager
  • FastBot2: 1.2.8

Проблема обнаружилась при получении большого сообщения Telegram через FastBot2.

Как воспроизводится

HTTP-ответ Telegram имел:

HTTP/1.1 200 OK
Content-Length: 20804

При прямом чтении:

stream->readBytes(buffer, 20804);

один из тестов получил:

EXPECTED: 20804
RECEIVED: 15994
DIFFERENCE: 4810

При этом соединение не было разорвано, и оставшиеся данные можно было получить последующими операциями чтения.

Из-за этого текущая реализация:

bool parse(Stream* stream, size_t length) {
    if (!stream || !length || !json.resize(length)) return 0;

    if (stream->readBytes(json, length) != length) {
        json.reset();
        return 0;
    }

    return Parser::parse(json.buf(), length);
}

возвращает false.

Дополнительная проверка

Для проверки, что проблема именно в способе чтения, был сделан тест с последовательным чтением до получения полного размера.

После этого те же данные Telegram:

EXPECTED LENGTH: 20804
PARSE RESULT: OK
PARSER ELEMENTS: 18
RESPONSE SIZE: 20804

разбираются корректно.

Тест повторялся несколько раз и стабильно получал все 20804 байта.

Проверка через FastBot2

После изменения ParserStream полный путь:

Telegram
  ↓
WiFiClientSecure
  ↓
GyverHTTP
  ↓
StreamReader
  ↓
ParserStream
  ↓
GSON
  ↓
FastBot2

также начал работать корректно.

До исправления FastBot2 получал:

> got json
RAW RESPONSE LENGTH: 0

После исправления:

> got json

========== RAW CALLBACK ==========
RAW LENGTH: 20804
RAW FIRST: {"ok":true,"result":[...
==================================

Причём это успешно повторялось для нескольких больших сообщений.

Предлагаемое исправление

Вместо одного вызова readBytes() необходимо читать данные в цикле до получения всех length байт:

bool parse(Stream* stream, size_t length) {
    if (!stream || !length || !json.resize(length)) return 0;

    size_t received = 0;

    while (received < length) {
        if (!stream->available()) {
            delay(1);
            continue;
        }

        size_t available = stream->available();
        size_t need = length - received;
        size_t block = min(available, need);

        size_t n = stream->readBytes((char*)json.buf() + received, block);

        if (!n) {
            json.reset();
            return 0;
        }

        received += n;
    }

    return Parser::parse(json.buf(), length);
}

Этот вариант успешно проверен на реальном HTTP-ответе размером 20804 байта через WiFiClientSecure.

Важное замечание

В приведённом варианте отсутствует дополнительный timeout самого цикла чтения. Это сделано намеренно для минимального воспроизведения проблемы.

Возможно, в окончательной реализации следует использовать timeout исходного Stream, чтобы при обрыве соединения цикл не ожидал данные бесконечно.

Главная проблема, однако, заключается именно в предположении, что:

readBytes(buffer, length) == length

обязательно выполняется для сетевого Stream.

Для WiFiClientSecure это предположение не выполняется.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions