Проект: Создание двоичного сообщения
Используйте явный порядок байтов и ULEB128 и отклоняйте короткий ввод.
Wave Foundation
формат сообщения
Первые 2 байта хранят номера типов big-endian u16, а затем значения ULEB128 u64. Если вы записываете структурную память в файл как есть, на нее будут влиять заполнение и порядок байтов, поэтому кодируйте ее по полям.
Сохраните его как main.wave и запустите.
import("std::bytes::types")::{
ByteReader, ByteWriter
};
import("std::bytes::cursor")::{
bytes_reader, bytes_writer, bytes_writer_write_be_u16, bytes_reader_read_be_u16
};
import("std::bytes::leb128")::{
bytes_writer_write_uleb128_u64, bytes_reader_read_uleb128_u64
};
fun main() -> i32 {
var data: array<u8, 12>;
var writer: ByteWriter = bytes_writer(&data[0], 12);
if (bytes_writer_write_be_u16(&writer, 7) < 0) {
return 1;
}
if (bytes_writer_write_uleb128_u64(&writer, 300) < 0) {
return 2;
}
var reader: ByteReader = bytes_reader(&data[0], writer.position);
var kind: u16 = 0;
var value: u64 = 0;
if (bytes_reader_read_be_u16(&reader, &kind) < 0) {
return 3;
}
if (bytes_reader_read_uleb128_u64(&reader, &value) < 0) {
return 4;
}
println("kind={} value={} bytes={}", kind, value, reader.position);
var short: ByteReader = bytes_reader(&data[0], 1);
kind = 99;
if (bytes_reader_read_be_u16(&short, &kind) >= 0) {
return 5;
}
println("preserved={} {}", short.position, kind);
return 0;
}
Результат выполнения:
kind=7 value=300 bytes=4
preserved=0 99
Что проверить
В reader мы передаем фактическую записанную длину, а не общую емкость массива, равную 12. Это сделано для того, чтобы избежать чтения неинициализированных конечных байтов в качестве входных данных. Даже если короткое чтение ввода не удалось, position=0 и kind=99 сохраняются.
Расширенные упражнения и комментарии
Если формат не допускает дополнительных байтов в конце, отметьте reader.position == reader.len после завершения синтаксического анализа. Добавляя поле длины, убедитесь, что оно не превышает оставшиеся байты входных данных и что вычисление длины +offset не выходит за пределы диапазона.
Посмотрите на фактические байты
Номер типа 7 — big-endian u16 и, следовательно, 00 07. Значение 300 становится от ULEB128 до AC 02. Все сообщение состоит из следующих четырех байтов:
00 07 AC 02
───── ─────
종류 값
ULEB128 использует младшие 7 бит для каждого байта значения, и если старший бит равен 1, это означает, что за ним следует следующий байт. Младшие 7 бит числа 300 равны 44, а остаток равен 2. Первый байт равен 44 плюс 128 последовательных меток, или 172, или 0xAC. В последнем байте 0x02 знака продолжения нет.
Емкость и продолжительность использования
u16 использует 2 байта, а ULEB128 из u64 использует до 10 байт, поэтому 12-байтовый массив может хранить оба поля. Значение 300 использует только 2 байта, что делает фактическое сообщение размером 4 байта. При отправке в файл или сокет вы отправляете байт writer.position, а не весь массив.
position в reader — текущая позиция чтения. После прочтения типа он становится 2, а после прочтения значения — 4. Чтобы при возникновении ошибки перейти к следующему сообщению, необходимо знать границу неудавшегося сообщения. Политика восстановления сообщений не устанавливается автоматически просто потому, что функция чтения сохраняет местоположение.
Упражнение на определение граничных значений
Измените значения на 0, 127, 128, 16383, 16384, чтобы определить длину кодирования. При изменении со 127 на 128 длина ULEB128 увеличивается с 1 до 2, а при изменении с 16383 на 16384 длина увеличивается с 2 до 3. Вычислите общую длину, включая 2 байта поля типа.
Также проверяются сообщения, у которых урезан последний байт. Если исходная длина сообщения была равна 4, мы передаем длину 3 в reader. Поле типа считывается, но чтение значения должно завершиться неудачей, поскольку последний байт ULEB128 отсутствует. В это время проверьте, сохраняются ли положение 2 и выходное значение непосредственно перед чтением ULEB128.