iproto

package module
v0.0.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 5, 2025 License: MIT Imports: 1 Imported by: 0

README

iproto - библиотека для сериализации структур в формате протокола iproto.

[[TOC]]

Использование

Для сериализации используются методы MarshalIProto и UnmarshalIProto:

type Marshaler interface {
	MarshalIProto([]byte) ([]byte, error)
}

type Unmarshaler interface {
	UnmarshalIProto([]byte) ([]byte, error)
}

type MarshalerUnmarshaler interface {
	Marshaler
	Unmarshaler
}

MarshalIProto дописывает в переданный буфер сериализованное представление типа (при помощи append). При реализации сетевых протоколов следует предвыделять буфер с достаточной ёмкостью (cap) для избежания избыточных аллокаций.

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

iprotogen генерирует реализацию этих методов для пользовательских типов, помеченных служебным комментарием //adv:iproto::

// MyInt - my defined type
//
//adv:iproto:ber
type MyInt int

// MyStruct - my defined struct type
//
//adv:iproto:
type MyStruct {
    ID uint32     // по дефолту, используется тот же тип, что указан в коде
    Number uint64 `iproto:"ber"`
    Data []int    `iproto:"u16,i32"`
}

// block-level comment
//adv:iproto:
type (
    MyStr string
    //adv:iproto:"ber"
    MyBER uint // переопределяет дефолтный комментарий
)

Комментарии должны иметь тот же формат, что и теги структур. Если комментарий присутствует у блока type, он действует на все типы, указанные в нём, для которых не указан отдельный комментарий.

Тип поля структуры можно указать тегом iproto. Если любое поле структуры имеет такой тег (включая поля, не поддерживаемые iprotogen, например _) - структура будет обработана генератором, даже если не указан комментарий //adv:iproto.

Директивы уровня файла (комментарии перед package без пробелов после //) копируются из исходного файла в генерируемый, это нужно для корректной поддержки build tags (см. опцию -tags).

Запуск генератора

Для подключения генератора к проекту (модулю) надо выполнить команду:

$ go get -tool github.com/adventures-team/go-iproto/cmd/iprotogen

Для использования генератора достаточно прописать в одном из файлов пакета:

//go:generate go tool iprotogen

и запустить команду go generate. Будет обработан весь пакет, а не только файл, содержащий строку go:generate.

Опции командной строки генератора:

  • -in (или $GOFILE для использования из go generate) - входной файл.
  • -tags - использовать сборочные теги (директива //go:build). Аналогично go -tags или golangci-lint --build-tags.
  • -only - обрабатывать только указанный файл. По умолчанию обрабатываются все файлы пакета. Так сделано, т.к. накладные расходы на загрузку пакета сравнимы с загрузкой одного файла.
  • -r - обрабатывать вложенные пакеты (в подпапках)
  • -tests - обрабатывать тесты (файлы *_tests.go). По умолчанию, тесты игнорируются.

Опции для отладки самого iprotogen:

  • -no-format - не обрабатывать результат генерации при помощи goimports. Может сгенерироваться код с избыточными импортами.
  • -stdout - распечатать результат генерации на stdout. По умолчанию, результаты сохраняются в файлах "$(orig_name)_generated.go".
  • -ignore-gen - не обрабатывать файлы, сгенерённые iprotogen. По умолчанию файлы парзятся, но методы в них игнорятся. Опция вызовет проблемы с генерацией, если генерёнными методами пользуются в этом же пакете.
  • -disable-strip - не использовать копирование исходника с обрезанием тел методов (копируется самый внешний модуль целиком, а не только один файл) во временную директорию. Повышает производительность, но могут возникнуть сложности при изменении обрабатываемых типов.
  • -keep-stripped - не удалять временную директорию.

Поддерживаемые типы

  • Все целочисленные (знаковые и беззнаковые) типы определённого размера: int8, int16, int32, int64, uint8, uint16, uint32, uint64.
  • Для целочисленных типов неопределённого размера (int и uint) необходимо явно указать формат в директиве/теге.
  • Типы с плавающей запятой: float32 и float64.
  • Строки.
  • Слайсы и массивы поддерживаемых типов (включая слайсы, структуры и указатели).
  • Map с ключём и значением любого поддерживаемого типа.
  • bool (возможно указать значение для true и false, по умолчанию - байты 1 и 0 соответственно).
  • Структуры с полями поддерживаемых типов. Сериализуются все публичные поля. Изменить дефолтный тип можно указанием тега.
  • Указатели на поддерживаемые типы.
  • Типы, определённые на базе поддерживаемых.

Теги

Для поддержки типа нужно добавить непосредственно перед его определением директиву //adv:iproto:

//adv:iproto:
type MyStruct struct {

В директиве также можно указать формат сериализации значения:

//adv:iproto:"ber"
type MyInt int

Аналогичным образом, можно указать формат для полей структур:

//adv:iproto:
type MyStruct struct {
    ID int `iproto:"ber"`

Если для числового типа (включая целочисленные и типы с плавающей точкой) указан формат размера, не совпадающего с дефолтным, то в методах сериализации будут сгенерированы проверки на границы этих типов, при нарушении будет возвращена обёрнутая ошибка iproto.ErrOverflow.

Тип iproto Дефолт для типов Описание
-, skip Тип/поле не используется
u8 uint8 aka byte
u16 uint16
u32 uint32, float32, длина слайса
u64 uint64, float64
i8 int8
i16 int16
i32 int32
i64 int64
ber perl pack 'w'

Для слайсов и строк можно указать тип длины (по умолчанию, u32) и тип элемента. Для вложенных слайсов можно указать все типы длин и тип элемента через запятую. В этом примере длина слайса слайсов передаётся в формате ber, длина слайсов строк - u16, а длина каждой строки - u8:

type MyStruct struct {
    NestedSlice [][]string `iproto:"ber,u16,u8"`
}

Для целочисленных типов, размер которых не фиксирован спецификацией (int и uint) указание тега обязательно:

//adv:iproto:"ber,u16"
type MySlice []uint

Для массивов длина не передаётся, если принимающая/передающая сторона (например, perl) использует длину - следует воспользоваться слайсом. Массивы следует рассматривать как структуры с нумерованными полями.

Для map можно указать тип длины, один тег для ключа и произвольное кол-во тегов для значения.

//adv:iproto:"ber,u8,u16,i32"
type MapStringInts map[string][]int

В этом примере длина map передаётся в формате ber, длина строки-ключа - в формате u8, количество интов в значении - u16, сами инты - в формате i32.

Map с пустыми значениями имеет такое же бинарное представление, как и слайс аналогичного типа (передаётся только ключи), и поддержка map (dict, hash и т.д.) на принимающей/передающей стороне не требуется:

//adv:iproto:"ber,u8"
type Strings []string

//adv:iproto:"ber,u8"
type StringSet map[string]struct{}

Булевские типы передаются в виде одного байта, отдельно можно задать значения для true (по умолчанию, 0x01) и false (0x00). Значение можно указать в числовом виде (в десятичном, восьмеричном с префиксом 0 или 0o, или шестнадцатеричном с префиксом 0x), либо в виде символа (в апострофах):

//adv:iproto:
type Bools struct {
    Default    bool                                  // true - 1,   false - 0
    Custom     bool `iproto:"true: 49, false: 48"`   // true - 49,  false - 48
    CustomTrue bool `iproto:"true: 0xFF"`            // true - 255, false - 0
    CustomChar bool `iproto:"true: 't', false: 'f'"`
}

Предопределённые типы

Для сериализации простых типов можно воспользоваться пакетом iprototypes (github.com/adventures-team/go-iproto/types). В нём определены типы:

  • В формате little endian: Uint8 (алиас Byte), Uint16, Uint32 (алиас Rune), Uint64, Int8, Int16, Int32, Int64, Float32, Float64.
  • В формате BER BER (uint64)
  • С длиной в формате BER: String, Bytes, Slice

При кодировании простых типов ошибку можно игнорировать, при декодировании - не стоит.

Пример:

    buf, _ = iprototypes.String(str).MarshalIProto(buf)

Поддержка сериализации для произвольных типов

Для самостоятельной сериализации своих типов можно определить методы MarshalIProto и UnmarshalIProto. Если определён только один из методов, то для всех типов, использующих данный тип (например, слайсы или структуры с полями этого типа), будет сгенерирован только этот метод. Так сделано для избежания ошибок симметричности (сериализовали в своём формате, попытались десериализовать в дефолтном). Если зачем-то нужно определить только одну из операций, а другу генерить по дефолту, см. пример: tests/asymmetrical_test.go.

TODO

Обязательно:

  • Декодирование слайсов интерфейсов
  • Научиться парзить директивы типов, включённых в структуру (как встроенных, так и именованных) (см. addons.CreationTime)

BUGS:

  • Если объявить несимметричные маршалеры, и встроить тип с симметричными (например, генерёнными), то будет генериться оба метода, при том, что надо генерить только один.
  • Не генерить обёртку, вызывающую Marshal/Unmarshal встроенного поля, в случае тривиального встраивания (см. tp/test/addons_retry_generated_test.go)
  • Вообще, продумать логику обработки встраивания, что разрешить, что запретить, и документировать это.

Q5:

  • Не генерировать избыточные приведения типов для алиасов (не забываем про 1.23+ gotypesalias) стандартных типов. Нужно переделать имеющийся механизм (isInt... и т.д.) на этапе парзинга, по аналогии с поддержкой []byte
  • Переменные для сокращения длинных конструкций
  • Поддержка нескольких тегов для ключа map (только когда понадобится в продуктовом проекте)
  • marshaler pointer receiver (оптимизация для больших структур)
  • Переиспользование уже выделенных указателей/слайсов/мэпов (?)
  • Для типов, реализующих "пустой" маршалер, по прежнему генерится вызов Marshal/Unmarshal. Проверить, что эти конструкции в асмовом коде отсутствуют (оптимизатор), чтобы не городить over-engineering по вырезанию этого кода.

Первые два пункта влияют только на читаемость генерённого кода, на результате компиляции это не отразится.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrBEROverflow   = errors.New("BER: 64 bit overflow")
	ErrIncompleteBER = errors.New("BER: unexpected end of data")
)

Ошибки DecodeBER

View Source
var (
	ErrSizeMismatch = errors.New("length mismatch while decoding for type")
	ErrOverflow     = errors.New("overflow")
)

Ошибки

Functions

func BERSize

func BERSize(n uint64) int

BERSize возвращает кол-во байт, необходимое для кодирования числа n.

func DecodeBER

func DecodeBER(ber []byte) (res uint64, tail []byte, err error)

DecodeBER - распаковка инта из формата произвольной длины, совместимого с perl unpack 'w' (aka ULEB128). tail - остаток буфера после распаковки.

Списано с небольшими правками из github.com/funny-falcon/go-iproto/sbox

func EncodeBER

func EncodeBER(buf []byte, n uint64) []byte

EncodeBER - упаковка инта в формат произвольной длины, совместимый с perl pack 'w' (aka ULEB128).

Списано с небольшими правками из github.com/funny-falcon/go-iproto/sbox

Types

type Marshaler

type Marshaler interface {
	MarshalIProto([]byte) ([]byte, error)
}

type MarshalerUnmarshaler

type MarshalerUnmarshaler interface {
	Marshaler
	Unmarshaler
}

type Unmarshaler

type Unmarshaler interface {
	UnmarshalIProto([]byte) ([]byte, error)
}

Directories

Path Synopsis
cmd
iprotogen command
iprotogen - iproto serialization code generator.
iprotogen - iproto serialization code generator.
strippkg command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL