binary

package
v1.5.0 Latest Latest
Warning

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

Go to latest
Published: Jun 3, 2019 License: Apache-2.0 Imports: 12 Imported by: 0

README

Binary to Map Encoder

Encode and decode arbitrary binary structure data in golang.

Notes and Caveats
  • Bitfields are not support at this time
  • Presently this library only supports decoding from arbitary binary structures to a go map[string]interface{}. Encoding is in progress.
  • This project is based on the kaitai project and supports a subset of the primitives. Using simple yaml based spec, conversion from binary to other structures or json can be performed simply. The primary difference between this an kaitai is the ablity to decode arbitrary data without have to pre-compile structure source.

Sample Data

The examples use the following C stucture, encoded to base64.

C Structure
#include <string.h>
#include <stdint.h>

typedef struct tagSensors {
    double ph;
    double ec;
} Sensors;

typedef struct tagReading {
    char probe_id[32];
    double temp;
    double humidity;
    Sensors sensors;
} Reading;

Reading N={ "000001", 12.0, 0.88, { 0.34, 0.45 }};
Base64 Value

The Reading data would have this value on the wire (base64).

MDAwMDAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAKAAAAAAAAD/sKPXCj1wpP9XCj1wo9cM=

Sample Spec

The spec follows the kaitai format as closely as possible. There are two additional fields for sequence values that are supported.

  1. omit - The value will be omitted from the final input.
  2. encodedValue - evaluation expression for the encoding the value (not yet supported).
meta:
  id: iot_reading
  endian: be
seq:
  - id: probe_id
    type: str
    size: 32
  - id: temp
    type: f8
    doc: the temperature
  - id: humidity_raw
    type: f8
    doc: the raw humidity
    omit: true
  - id: sensors
    type: sensor_data
types:
  sensor_data:
    seq:
      - id: ph_raw
        type: f8
        omit: true
      - id: ec_raw
        type: f8
        omit: true
    instances:
      pH:
        value: seq(sensors, 'ph_raw') * 100
      ec:
        value: ec_decoder() / 2.3
instances:
  humidity:
    value: humidity_raw * 100
Spec Components
Sequences

The spec seq is an array of binary data types defined in order in the data, starting at index 0. Each sequence must have an id and an optional type. The default type will return a []byte. If type is omitted, size should be provided, otherwise it is assumed the sequence continues to the end-of-stream (eos).

Sequences can be either primitives or user defined types. Primitives are defined in the format:

<type><width><endian>

Primitive Types
  • u - unsigned integer
  • s - signed integer
  • f - floating point
Primitive Width

Signed (s) and Unsigned (u) integers support 1 (8-bit), 2 (16-bit), 4 (32-bit), and 8 (64-bit) widths.

Floating points support 4 (32-bit) and double 8 (64-bit) widths.

Endianess
  • be - Big Endian
  • le - Little Endian (default)
String Types

str - The str type will decode a string of UTF-8 byte characters. This type requires the size attribute be specified in the sequence.

strz - The strz type will decode a null \0 terminated string.

Instances

The spec instances hash contains named components that either have relative positioning in the data, or a derived value.

User defined types

The spec types is a hash of named types that can be use in the sequences or other types.

Initializing and Decoding the Spec

specData, err := ioutil.ReadFile("spec.yaml")
if err != nil {
    panic(err)
}

spec, err := binary.NewSpec(specData)
if err != nil {
    panic(err)
}
Initializing with Custom Functions
spec, err := binary.NewSpec(specData, map[string]binary.EvalFunc{
    "ec_decoder":
        func(ctx types.StringMap, args ...interface{}) (interface{}, error) {
            return ctx.Float64("sensors.ec_raw") * 1000, nil
    },
})
if err != nil {
    panic(err)
}

Decoding the Data and converting to JSON

Data is expected to be raw bytes.

// data is the raw []byte, base64 decoded value
rval, err := spec.Decode(data)
if err != nil {
    panic(err)
}

out, err := json.MarshalIndent(rval, "", "\t")
if err != nil {
    panic(err)
}

Output:

{
  "humidity": 88,
  "probe_id": "000001",
  "sensors": {
    "ec": 195.6521739130435,
    "pH": 34
  },
  "temp": 12
}

Decoding to a struct


import "gitlab.com/ModelRocket/sparks/util"

type reading struct {
    ProbeID  string `json:"probe_id"`
    Humidity int64
    Sensors  struct {
        EC float64
        PH float64
    }
    Temp float64
}

func main() {
    // ...

    r := &reading{}
    if err := util.MapStruct(r, rval); err != nil {
        panic(err)
    }
}

Advanced Sequence and Instance Values

Repeating and array values are supported to kaitai spec. For example this C structure:

#include <string.h>
#include <stdint.h>

typedef struct tagSensors {
    char name[16];
    double value;
} Sensors;

typedef struct tagReading {
    int8_t vals_len;
    Sensors vals[2];
} Reading;

Reading N={1, {{"ph", 12.0}, { "ec", 0.88}}};

Would be represented as:

meta:
  id: iot_reading
  endian: be
seq:
  - id: vals_len
    type: s1
    omit: true
  - id: vals
    type: sensor_data
    repeat: expr
    repeat-expr: vals_len
types:
  sensor_data:
    seq:
      - id: name
        type: str
        size: 16
      - id: value
        type: f8
    instances:
      value_converted:
        value: context('vals.0.value') * 100
Accessing other values in selectors

There are two special builtin function that allows for accesing values inside of other structs:

  • seq(member, key) - returns a sub for an embedded member

Example: seq(sensors, 'ph_raw')

  • context(key) - returns a value in the current context

Example: context('_inst.value')

Note: in this example, the special key _inst is used to access the current array value being operated on. A companion key to this is _index which gives the current index of the array value being operated on.

types:
  sensor_data:
    seq:
      - id: name
        type: str
        size: 16
      - id: value
        type: f8
    instances:
      value_real:
        value: context('_inst.value') * 100
{
  "vals": [
    {
      "name": "ph",
      "value": 12,
      "value_real": 1200
    },
    {
      "name": "ec",
      "value": 0.88,
      "value_real": 88
    }
  ]
}
Accessing values in arrays

Arrays can be accessed using a subscript in the dot notation i.e. context('vals.0.value') * 100

types:
  sensor_data:
    seq:
      - id: name
        type: str
        size: 16
      - id: value
        type: f8
    instances:
      value_total:
        value: context('vals.0.value') * 100

Documentation

Overview

Package binary provides helpers for encoding binary data from a spec file very loosely based on kaitai; see https://doc.kaitai.io.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ReadF32 added in v1.3.4

func ReadF32(rdr io.Reader, endian binary.ByteOrder) float32

ReadF32 reads a 32-bit float

func ReadF64 added in v1.3.4

func ReadF64(rdr io.Reader, endian binary.ByteOrder) float64

ReadF64 reads a 64-bit float

func ReadS16 added in v1.3.4

func ReadS16(rdr io.Reader, endian binary.ByteOrder) int16

ReadS16 reads an signed 16-bit integer

func ReadS32 added in v1.3.4

func ReadS32(rdr io.Reader, endian binary.ByteOrder) int32

ReadS32 reads an signed 32-bit integer

func ReadS64 added in v1.3.4

func ReadS64(rdr io.Reader, endian binary.ByteOrder) int64

ReadS64 reads an signed 64-bit integer

func ReadS8 added in v1.3.4

func ReadS8(rdr io.Reader, endian binary.ByteOrder) int8

ReadS8 reads an signed 8-bit integer

func ReadU16 added in v1.3.4

func ReadU16(rdr io.Reader, endian binary.ByteOrder) uint16

ReadU16 reads an unsigned 16-bit integer

func ReadU32 added in v1.3.4

func ReadU32(rdr io.Reader, endian binary.ByteOrder) uint32

ReadU32 reads an unsigned 32-bit integer

func ReadU64 added in v1.3.4

func ReadU64(rdr io.Reader, endian binary.ByteOrder) uint64

ReadU64 reads an unsigned 64-bit integer

func ReadU8 added in v1.3.4

func ReadU8(rdr io.Reader, endian binary.ByteOrder) uint8

ReadU8 reads an unsigned 8-bit integer

Types

type EvalFunc added in v1.3.7

type EvalFunc func(ctx types.StringMap, args ...interface{}) (interface{}, error)

EvalFunc is an evalutation function that can be used in specs

type Instance added in v1.3.4

type Instance struct {
	Sequence `yaml:",inline"`

	// Pos is the position of the data in the byte stream
	Pos *string `yaml:"pos,omitempty"`

	// Value is the computed value of the instance object
	Value *string `yaml:"value,omitempty"`
	// contains filtered or unexported fields
}

Instance is an instance definition

type Meta

type Meta struct {
	ID       string `yaml:"id"`
	Endian   string `yaml:"endian"`
	Encoding string `yaml:"encoding"`
}

Meta provides meta-information for the spec

type Sequence

type Sequence struct {
	// ID is the label for the field
	ID string `yaml:"id"`

	// Type defines the type, width, and byte order or a custom definde type
	Type string `yaml:"type"`

	// Size is the length of the field in bytes, or points to another sequence value
	Size *string `yaml:"size,omitempty"`

	// Repeat is used for array values
	Repeat *string `yaml:"repeat,omitempty"`

	// RepeatExpression is evaluated to determine the number of times to repeat
	RepeatExpression *string `yaml:"repeat-expr,omitempty"`

	// Omit when set will omit the sequence value from the final result
	Omit bool `yaml:"omit"`
	// contains filtered or unexported fields
}

Sequence is a field definition

type Spec

type Spec struct {
	Meta      Meta                 `yaml:"meta"`
	Sequence  []*Sequence          `yaml:"seq"`
	Instances map[string]*Instance `yaml:"instances"`
	Types     map[string]*Type     `yaml:"types,omitempty"`
	// contains filtered or unexported fields
}

Spec defines the specification

func NewSpec added in v1.3.7

func NewSpec(data []byte, evalFuncs ...map[string]EvalFunc) (*Spec, error)

NewSpec parses and initializes a new binary spec

func (*Spec) AddEvalFunc added in v1.3.7

func (s *Spec) AddEvalFunc(name string, handler EvalFunc)

AddEvalFunc add an evaluator function

func (*Spec) Decode added in v1.3.6

func (s *Spec) Decode(data []byte) (types.StringMap, error)

Decode decodes the binary data to a map using the spec

type Type added in v1.3.4

type Type struct {
	Sequence  []*Sequence          `yaml:"seq"`
	Instances map[string]*Instance `yaml:"instances"`
}

Type is a type definition

Jump to

Keyboard shortcuts

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