bcs

package
v1.4.1 Latest Latest
Warning

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

Go to latest
Published: Jan 3, 2025 License: Apache-2.0 Imports: 5 Imported by: 1

Documentation

Overview

Package bcs implements Binary Canonical Serialization BCS.

The bcs package can be used to serialize and deserialize complex types into a binary canonical format that is non-self describing. Meaning that you will need to know the format ahead of time in order to serialize and deserialize. Check out Serializer for serialization and Deserializer for deserialization.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Deserialize

func Deserialize(dest Unmarshaler, bytes []byte) error

Deserialize deserializes a single item from bytes.

This function will error if there are remaining bytes.

func DeserializeOption added in v1.2.0

func DeserializeOption[T any](des *Deserializer, deserialize func(des *Deserializer, out *T)) *T

DeserializeOption deserializes an optional value

Under the hood, this is represented as a 0 or 1 length array

Here's an example for handling an optional value:

// For a Some(10) value
bytes == []byte{0x01, 0x0A}
des := NewDeserializer(bytes)
output := DeserializeOption(des, nil, func(des *Deserializer, out *uint8) {
	out = des.U8()
})
// output == &10

// For a None value
bytes2 == []byte{0x00}
des2 := NewDeserializer(bytes2)
output := DeserializeOption(des2, nil, func(des *Deserializer, out *uint8) {
	out = des.U8()
})
// output == nil

func DeserializeSequence

func DeserializeSequence[T any](des *Deserializer) []T

DeserializeSequence deserializes an Unmarshaler implementation array

This lets you deserialize a whole sequence of Unmarshaler, and will fail if any member fails. All sequences are prefixed with an Uleb128 length.

func DeserializeSequenceWithFunction

func DeserializeSequenceWithFunction[T any](des *Deserializer, deserialize func(des *Deserializer, out *T)) []T

DeserializeSequenceWithFunction deserializes any array with the given function

This lets you deserialize a whole sequence of any type, and will fail if any member fails. All sequences are prefixed with an Uleb128 length.

func Serialize

func Serialize(value Marshaler) (bytes []byte, err error)

Serialize serializes a single item

type MyStruct struct {
  num uint64
}

func (str *MyStruct) MarshalBCS(ser *Serialize) {
	ser.U64(num)
}

struct := &MyStruct{ num: 100 }
bytes, _ := Serialize(struct)

func SerializeBool

func SerializeBool(input bool) ([]byte, error)

SerializeBool Serializes a single boolean

bytes, _ := SerializeBool(true)

func SerializeBytes

func SerializeBytes(input []byte) ([]byte, error)

SerializeBytes Serializes a single byte array

input := []byte{0x1, 0x2}
bytes, _ := SerializeBytes(input)

func SerializeOption added in v1.2.0

func SerializeOption[T any](ser *Serializer, input *T, serialize func(ser *Serializer, item T))

SerializeOption serializes an optional value

Under the hood, this is represented as a 0 or 1 length array

Here's an example for handling an optional value:

// For a Some(10) value
input := uint8(10)
ser := &Serializer{}
bytes, _ := SerializeOption(ser, &input, func(ser *Serializer, item uint8) {
	ser.U8(item)
})
// bytes == []byte{0x01,0x0A}

// For a None value
ser2 := &Serializer{}
bytes2, _ := SerializeOption(ser2, nil, func(ser *Serializer, item uint8) {
	ser.U8(item)
})
// bytes2 == []byte{0x00}

func SerializeSequence

func SerializeSequence[AT []T, T any](array AT, ser *Serializer)

SerializeSequence serializes a sequence of Marshaler implemented types. Prefixed with the length of the sequence.

It works with both array values by reference and by value:

type MyStruct struct {
	num uint64
}

func (str *MyStruct) MarshalBCS(ser *Serialize) {
	ser.U64(num)
}

myArray := []MyStruct{
	MyStruct{num: 0},
	MyStruct{num: 1},
	MyStruct{num: 2},
}

serializer := &Serializer{}
SerializeSequence(myArray, ser)
bytes := serializer.ToBytes()

func SerializeSequenceOnly

func SerializeSequenceOnly[AT []T, T any](input AT) ([]byte, error)

SerializeSequenceOnly serializes a sequence into a single value using SerializeSequence

type MyStruct struct {
	num uint64
}

func (str *MyStruct) MarshalBCS(ser *Serialize) {
	ser.U64(num)
}

myArray := []MyStruct{
	MyStruct{num: 0},
	MyStruct{num: 1},
	MyStruct{num: 2},
}

bytes, err := SerializeSequenceOnly(myArray)

func SerializeSequenceWithFunction

func SerializeSequenceWithFunction[AT []T, T any](array AT, ser *Serializer, serialize func(ser *Serializer, item T))

SerializeSequenceWithFunction allows custom serialization of a sequence, which can be useful for non-bcs.Struct types

array := []string{"hello", "blockchain"}

SerializeSequenceWithFunction(array, func(ser *Serializer, item string) {
	ser.WriteString(item)
}

func SerializeSingle

func SerializeSingle(marshal func(ser *Serializer)) (bytes []byte, err error)

SerializeSingle is a convenience function, to not have to create a serializer to serialize one value

Here's an example for handling a nested byte array

input := [][]byte{[]byte{0x1}, []byte{0x2}}
bytes, _ := SerializeSingle(func(ser *Serializer) {
	ser.Uleb128(len(input))
	for _, list := range input {
		ser.WriteBytes(list)
	}
})

func SerializeU128

func SerializeU128(input big.Int) ([]byte, error)

SerializeU128 Serializes a single uint128

u128 := big.NewInt(1)
bytes, _ := SerializeU128(u128)

func SerializeU16

func SerializeU16(input uint16) ([]byte, error)

SerializeU16 Serializes a single uint16

bytes, _ := SerializeU16(uint16(50000))

func SerializeU256

func SerializeU256(input big.Int) ([]byte, error)

SerializeU256 Serializes a single uint256

u256 := big.NewInt(1)
bytes, _ := SerializeU256(u256)

func SerializeU32

func SerializeU32(input uint32) ([]byte, error)

SerializeU32 Serializes a single uint32

bytes, _ := SerializeU32(uint32(50000))

func SerializeU64

func SerializeU64(input uint64) ([]byte, error)

SerializeU64 Serializes a single uint64

bytes, _ := SerializeU64(uint64(20))

func SerializeU8

func SerializeU8(input uint8) ([]byte, error)

SerializeU8 Serializes a single uint8

bytes, _ := SerializeU8(uint8(200))

Types

type Deserializer

type Deserializer struct {
	// contains filtered or unexported fields
}

Deserializer is a type to deserialize a known set of bytes. The reader must know the types, as the format is not self-describing.

Use NewDeserializer to initialize the Deserializer

bytes := []byte{0x01}
deserializer := NewDeserializer(bytes)
num := deserializer.U8()
if deserializer.Error() != nil {
	return deserializer.Error()
}

func NewDeserializer

func NewDeserializer(bytes []byte) *Deserializer

NewDeserializer creates a new Deserializer from a byte array.

func (*Deserializer) Bool

func (des *Deserializer) Bool() bool

Bool deserializes a single byte as a bool

func (*Deserializer) Error

func (des *Deserializer) Error() error

Error If there has been any error, return it

func (*Deserializer) ReadBytes

func (des *Deserializer) ReadBytes() []byte

ReadBytes reads bytes prefixed with a length

func (*Deserializer) ReadFixedBytes

func (des *Deserializer) ReadFixedBytes(length int) []byte

ReadFixedBytes reads bytes not-prefixed with a length

func (*Deserializer) ReadFixedBytesInto

func (des *Deserializer) ReadFixedBytesInto(dest []byte)

ReadFixedBytesInto reads bytes not-prefixed with a length into a byte array

func (*Deserializer) ReadString

func (des *Deserializer) ReadString() string

ReadString reads UTF-8 bytes prefixed with a length

func (*Deserializer) Remaining

func (des *Deserializer) Remaining() int

Remaining tells the remaining bytes, which can be useful if there were more bytes than expected

bytes := []byte{0x01, 0x02}
deserializer := NewDeserializer(bytes)
num := deserializer.U8()
deserializer.Remaining == 1

func (*Deserializer) SetError

func (des *Deserializer) SetError(err error)

SetError If the data is well-formed but nonsense, UnmarshalBCS() code can set error

func (*Deserializer) Struct

func (des *Deserializer) Struct(v Unmarshaler)

Struct reads an Unmarshaler implementation from bcs bytes

This is used for handling types outside the provided primitives

func (*Deserializer) U128

func (des *Deserializer) U128() big.Int

U128 deserializes a single unsigned 128-bit integer

func (*Deserializer) U16

func (des *Deserializer) U16() uint16

U16 deserializes a single unsigned 16-bit integer

func (*Deserializer) U256

func (des *Deserializer) U256() big.Int

U256 deserializes a single unsigned 256-bit integer

func (*Deserializer) U32

func (des *Deserializer) U32() uint32

U32 deserializes a single unsigned 32-bit integer

func (*Deserializer) U64

func (des *Deserializer) U64() uint64

func (*Deserializer) U8

func (des *Deserializer) U8() uint8

U8 deserializes a single unsigned 8-bit integer

func (*Deserializer) Uleb128

func (des *Deserializer) Uleb128() uint32

Uleb128 deserializes a 32-bit integer from a variable length Unsigned LEB128

type Marshaler

type Marshaler interface {
	// MarshalBCS implements a way to serialize the type into BCS.  Note that the error will need to be directly set
	// using [Serializer.SetError] on the [Serializer].  If using this function, you will need to use [Serializer.Error]
	// to retrieve the error.
	MarshalBCS(ser *Serializer)
}

Marshaler is an interface for any type that can be serialized into BCS

It's highly suggested to implement on a pointer to a type, and not the type directly. For example, you could implement a simple Marshaler for a given struct.

type MyStruct struct {
  num     uint8
  boolean bool
}

func (str *MyStruct) MarshalBCS(ser *Serializer) {
  ser.U8(str.num)
  ser.Bool(str.boolean)
}

Additionally, if there is expected data, you can add errors to serialization. It's suggested to stop serialization after any errors.

type MyStruct struct {
  num     uint8 // Only allowed to be 0-10
  boolean bool
}

func (str *MyStruct) MarshalBCS(ser *Serializer) {
  if str.num > 10 {
    ser.SetError(fmt.Error("Cannot serialize MyStruct, num is greater than 10: %d", str.num)
    return
  }
  ser.U8(str.num)
  ser.Bool(str.boolean)
}

type Serializer

type Serializer struct {
	// contains filtered or unexported fields
}

Serializer is a holding type to serialize a set of items into one shared buffer

serializer := &Serializer{}
serializer.U64(uint64(10))
serializedBytes := serializer.ToBytes()

func (*Serializer) Bool

func (ser *Serializer) Bool(v bool)

Bool serialize a bool into a single byte, 0x01 for true and 0x00 for false

func (*Serializer) Error

func (ser *Serializer) Error() error

Error the error if serialization has failed at any point

func (*Serializer) FixedBytes

func (ser *Serializer) FixedBytes(v []byte)

FixedBytes similar to Serializer.WriteBytes, but it forgoes the length header. This is useful if you know the fixed length size of the data, such as AccountAddress

func (*Serializer) Reset

func (ser *Serializer) Reset()

Reset clears the serializer to be reused

func (*Serializer) SetError

func (ser *Serializer) SetError(err error)

SetError If the data is well-formed but nonsense, [Marshaler.MarshalBCS] code can set the error.

func (*Serializer) Struct

func (ser *Serializer) Struct(v Marshaler)

Struct uses custom serialization for a Marshaler implementation.

func (*Serializer) ToBytes

func (ser *Serializer) ToBytes() []byte

ToBytes outputs the encoded bytes

func (*Serializer) U128

func (ser *Serializer) U128(v big.Int)

U128 serialize an unsigned 128-bit integer in little-endian format

func (*Serializer) U16

func (ser *Serializer) U16(v uint16)

U16 serialize an unsigned 16-bit integer in little-endian format

func (*Serializer) U256

func (ser *Serializer) U256(v big.Int)

U256 serialize an unsigned 256-bit integer in little-endian format

func (*Serializer) U32

func (ser *Serializer) U32(v uint32)

U32 serialize an unsigned 32-bit integer in little-endian format

func (*Serializer) U64

func (ser *Serializer) U64(v uint64)

U64 serialize an unsigned 64-bit integer in little-endian format

func (*Serializer) U8

func (ser *Serializer) U8(v uint8)

U8 serialize a byte

func (*Serializer) Uleb128

func (ser *Serializer) Uleb128(val uint32)

Uleb128 serialize an unsigned 32-bit integer as an Uleb128. This is used specifically for sequence lengths, and enums.

func (*Serializer) WriteBytes

func (ser *Serializer) WriteBytes(v []byte)

WriteBytes serialize an array of bytes with its length first as an Uleb128.

func (*Serializer) WriteString

func (ser *Serializer) WriteString(v string)

WriteString similar to Serializer.WriteBytes using the UTF-8 byte representation of the string

type Struct

type Struct interface {
	Marshaler
	Unmarshaler
}

Struct is an interface for an on-chain type. It must be able to be both Marshaler and Unmarshaler for BCS

type Unmarshaler

type Unmarshaler interface {
	// UnmarshalBCS implements a way to deserialize the type into BCS.  Note that the error will need to be directly set
	// using [Deserializer.SetError] on the [Deserializer].  If using this function, you will need to use [Deserializer.Error]
	// to retrieve the error.
	UnmarshalBCS(des *Deserializer)
}

Unmarshaler is an interface for any type that can be deserialized from BCS

It's highly suggested to implement on a pointer to a type, and not the type directly. For example, you could implement a simple Unmarshaler for a given struct. You will need to add any appropriate error handling.

type MyStruct struct {
  num     uint8
  boolean bool
}

func (str *MyStruct) UnmarshalBCS(des *Deserializer) {
  str.num = des.U8()
  str.boolean = des.Bool()
}

Additionally, if there is expected formatting errors, you can add errors to deserialization. It's suggested to stop serialization after any errors.

type MyStruct struct {
  num     uint8 // Only allowed to be 0-10
  boolean bool
}

func (str *MyStruct) UnmarshalBCS(des *Deserializer) {
  str.num = des.U8()
  if des.Error() {
    // End early, since deserialization failed
    return
  }
  if str.num > 10 {
    ser.SetError(fmt.Error("Cannot deserialize MyStruct, num is greater than 10: %d", str.num)
    return
  }
  str.boolean = des.Bool()
}

Jump to

Keyboard shortcuts

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