bytes: диапазоны, курсоры и ULEB128.

Описывает чтение/запись байтов длиной view и сохранение состояния в случае сбоя.

Wave Foundation

Чем он отличается от строки?

Байтовые данные могут содержать ноль, поэтому передайте указатель вместе с длиной. Bytes и BytesMut являются представлениями, не являющимися владельцами, и действительны только до тех пор, пока базовое хранилище остается действительным. BytesMut требует записываемого хранилища.

Создать cursor

std::bytes::cursor
bytes_reader(data: ptr<u8>, len: i64) -> ByteReader
bytes_writer(data: ptr<u8>, len: i64) -> ByteWriter
bytes_reader_read_be_u16(reader: ptr<ByteReader>, out_value: ptr<u16>) -> i32
bytes_writer_write_be_u16(writer: ptr<ByteWriter>, value: u16) -> i32

Импортируйте ByteReader и ByteWriter из std::bytes::types. Их поле позиции определяет местоположение следующей операции. Их длина равна общему количеству доступных байт. Создание любого курсора не копирует и не выделяет базовую память.

be — это big-endian, le — это little-endian. Если тип файла big-endian, используйте функцию be независимо от порядка байтов хоста CPU. Существуют 16, 32 и 64-битные signed/unsigned функции чтения/записи и однобайтовые функции.

Ошибки и сохранение состояния

BYTES_OK из std::bytes::errors равно 0. INVALID указывает на недопустимый диапазон, EOF недостаточная входная мощность, NO_SPACE недостаточная выходная мощность и OVERFLOW — значение, выходящее за пределы представимого диапазона. Проверенные операции с курсором перемещают позицию только после успешного завершения всей операции. Неудачное чтение также сохраняет выходное значение.

ULEB128

std::bytes::leb128
bytes_reader_read_uleb128_u64(reader: ptr<ByteReader>, output_value: ptr<u64>) -> i32
bytes_writer_write_uleb128_u64(writer: ptr<ByteWriter>, value: u64) -> i32

ULEB128 хранит 64-битное целое число без знака в переменном количестве байтов, используя не более 10 байтов. Если места недостаточно, средство записи сохраняет как свою позицию, так и байты назначения. Считыватель отличает неполный ввод от значения, превышающего u64. Допускаются терминированные, неминимальные кодировки.

Чтобы создать фактическое сообщение и увидеть кратковременную ошибку ввода, перейдите к Практика двоичных сообщений. Не пытайтесь вывести строку байтов, содержащую нули, как str.

Чтение одних и тех же байтов в разном порядке

Порядок байтов — это правило хранения чисел. Если вы прочитаете два байта 1 и 2 как big-endian, это будет 1×256+2, а если вы прочтете их как little-endian, это будет 2×256+1. Выбирайте на основе правил сети или типа файла.

import("std::bytes::types")::{Bytes, bytes_view};
import("std::bytes::read")::{bytes_read_be_u16, bytes_read_le_u16};

fun main() -> i32 {
    var data: array<u8, 2> = [1, 2];
    var view: Bytes = bytes_view(&data[0], 2);
    var big: u16 = 0;
    var little: u16 = 0;

    if (bytes_read_be_u16(view, 0, &big) < 0) {
        return 1;
    }

    if (bytes_read_le_u16(view, 0, &little) < 0) {
        return 2;
    }

    println("be={} le={}", big, little);
    return 0;
}

Результат выполнения:

be=258 le=513

view заимствует массив. Поскольку отдельного выделения или копирования нет, массив можно использовать только до тех пор, пока он действителен. offset измеряется в байтах, и для чтения 16 бит требуется 2 байта из этой позиции.

Выберите offset API и cursor API.

Функция read/write, принимающая аргумент offset, удобна для форматов, которые напрямую считывают указанную позицию поля. Для потоков, где следующая позиция зависит от длины предыдущего поля, удобно использовать cursor с position.

При смешивании этих двух материалов поясните, какой из них является стандартным: cursor.position или отдельный offset. Избегайте ошибки, связанной с добавлением одного и того же местоположения дважды или переходом к следующему местоположению без успешного чтения.

Проверьте статус по короткому входу

import("std::bytes::types")::{ByteReader};
import("std::bytes::cursor")::{bytes_reader, bytes_reader_read_be_u16};
import("std::bytes::errors")::{BYTES_ERROR_EOF};

fun main() {
    var data: array<u8, 1> = [1];
    var reader: ByteReader = bytes_reader(&data[0], 1);
    var value: u16 = 99;
    var status: i32 = bytes_reader_read_be_u16(&reader, &value);

    if (status == BYTES_ERROR_EOF) {
        println("position={} value={}", reader.position, value);
    }
}

Результат выполнения:

position=0 value=99

Два байта не нужны, поэтому выходное значение и местоположение сохраняются. Это свойство полезно в проектах, которые повторяют чтение того же поля после получения дополнительных входных данных. Однако если пространство хранения, указанное view, было перераспределено, адрес также необходимо обновить.

Граница ULEB128

0~127 использует один байт, начиная с 128 использует больше байтов. Старший бит каждого байта указывает, следуют ли за ним данные. Значение за пределами u64 или слишком длинный непрерывный ввод — это OVERFLOW, что отличается от EOF, у которого просто меньше входных данных.

Непосредственно проверьте, сохраняет ли состояние «Недостаточная емкость» writer свое состояние.

import("std::bytes::types")::{ByteWriter};
import("std::bytes::cursor")::{bytes_writer};
import("std::bytes::leb128")::{bytes_writer_write_uleb128_u64};
import("std::bytes::errors")::{BYTES_ERROR_NO_SPACE};

fun main() {
    var data: array<u8, 1> = [85];
    var writer: ByteWriter = bytes_writer(&data[0], 1);
    var status: i32 = bytes_writer_write_uleb128_u64(&writer, 128);

    if (status == BYTES_ERROR_NO_SPACE) {
        println("position={} byte={}", writer.position, data[0]);
    }
}

Результат выполнения:

position=0 byte=85

Для 128 требуется два байта, но только один пробел. После сбоя остается и первый байт 85. Совместная проверка типов ошибок и сохранения состояния описывает границу лучше, чем простая проверка успеха roundtrip.

Последовательность создания парсера сообщений

  1. Прочтите фиксированный заголовок и проверьте тип и версию.
  2. Прочтите длину и сравните ее с оставшимся входным диапазоном.
  3. Передавайте только необходимые данные в view или в отдельный буфер.
  4. Если формат требует всего сообщения, дополнительные байты также проверяются.
  5. Различает EOF и ошибки недопустимого формата и передает их вызывающей стороне.

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