flatread

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2026 License: MIT Imports: 5 Imported by: 1

README

flatread

CI Go Reference

Read FlatBuffers buffers when you don't have the schema.

No dependencies. Every accessor is bounds-checked, so malformed input returns a zero value instead of panicking.

Why

The normal FlatBuffers workflow compiles a .fbs schema into generated accessors, and the field names live in that generated code. If you're handed a binary payload without its schema there's nothing to generate from, and the official library can't help, because it needs the types you're trying to work out.

That comes up more often than the docs suggest:

  • reverse engineering a protocol from a capture
  • inspecting a payload from a service that doesn't publish its schema
  • triaging a file that decodes on one machine but not another
  • checking what an SDK actually sends, rather than what its docs claim

flatread reads a buffer positionally. Fields are addressed by vtable slot number instead of by name. That's enough to walk an unknown buffer, and enough to write a real decoder once you've worked out which slot means what.

Install

go get github.com/Mootjelh/flatread
go install github.com/Mootjelh/flatread/cmd/flatdump@latest

The CLI

Point flatdump at a payload and it describes every populated field, guessing each one's type from its bytes:

$ flatdump payload.bin
root @0x40 (168 bytes)
  slot 4   scalar   u32=4242 u16=4242 u8=146
  slot 6   string   "hello"
  slot 8   bytes    3
  slot 10  table
    slot 4   scalar   u32=7 u16=7 u8=7
  slot 12  vector   2 tables
    [0]
      slot 4   scalar   u32=11 u16=11 u8=11
    [1]
      slot 4   scalar   u32=22 u16=22 u8=22

It reads stdin when given no file, so it drops into a pipeline:

curl -s https://example.com/payload | flatdump -depth 8

With -json the same walk comes out machine-readable, which is what you want once you are grepping across a directory of captures:

flatdump -json payload.bin | jq '.fields[] | select(.kind == "string")'

Comparing two captures

Capture the same request twice, or the same object before and after something changed, and ask which slots moved:

$ flatdump -diff before.bin after.bin
4              4242 -> 9999
6              "hello" -> "world"
10.4           7 -> 8
12[0].4        11 -> 12

The path is the slot route from the root: a dot for a nested table, brackets for an element of a table vector. That works without a schema, because a slot number is a stable handle whether or not you know what the slot means.

Exit codes follow diff(1): 0 when the buffers agree, 1 when they differ, 2 on an error. In code it is flatread.Diff(a, b), returning a []Change.

A slot present on only one side is reported rather than skipped, which is the case a walk over one buffer's slots quietly misses.

Buffers that are not plain

Two things are worth checking before you conclude a payload is empty.

Size-prefixed buffers. gRPC and any format that stores several messages back to back put a uint32 byte count in front of the buffer. flatdump detects that and says so; in code it is RootSizePrefixed, with IsSizePrefixed for the guess.

This matters because feeding such a buffer to plain Root does not fail loudly. It reads the size as the root offset, lands somewhere arbitrary, and usually reports a table with no fields at all:

$ flatdump -prefix no sized.bin
root @0xa8 (172 bytes)

No error, no fields, and identical to a buffer that genuinely has nothing in it.

File identifiers. A schema can attach four bytes of identifier directly after the root offset, and when it is there it is usually the fastest way to find out what you are holding, because it is picked to be human-readable:

$ flatdump payload.bin
file identifier "FLTR"
root @0x40 (168 bytes)
...

Nothing in the buffer records whether that field is present, so FileIdentifier returns the bytes plus whether they look like an identifier at all. A false there means "these are not plausible", not "this schema has none".

Vectors of structs

A struct is not a table. It is a fixed-size record stored inline, with no vtable and no offsets, so a vector of them is a count followed by raw bytes. A vector of bytes is also a count followed by raw bytes. Nothing in the buffer tells them apart, and Guess reports KindBytes for both.

That reads badly by default. Three 8-byte structs is 24 bytes of data, and the count in front of them is 3, so the dump says:

$ flatdump payload.bin
  slot 4   bytes    3  01 00 00

which looks like a tiny byte vector rather than three records. Tell it the element size and it becomes readable:

$ flatdump -struct 4:8 payload.bin
  slot 4   structs  3 x 8 bytes
    [0] 01 00 00 00 02 00 00 00   u32 1 2
    [1] 03 00 00 00 04 00 00 00   u32 3 4
    [2] 05 00 00 00 06 00 00 00   u32 5 6

The uint32 reading is shown beside the bytes rather than instead of them, on the same reasoning as the scalar line showing three widths at once: the field layout also comes from a schema, so which reading is right is exactly what you do not know yet.

The argument is path:size, and the path is slot numbers from the root, dotted. -struct 6:8 is slot 6 of the root; -struct 4.6:12 is slot 6 of the table in root slot 4. It has to be a path rather than a bare slot number, because the dump walks every nested table and a bare number would claim that slot in all of them, turning ordinary byte vectors into nonsense records. The flag is repeatable.

A size that cannot fit is reported rather than quietly falling back:

  slot 4   structs  no vector of 4096-byte elements fits here

You asked a question about a schema and that is the answer to it. Falling back to the byte reading would show you the answer to a different one.

In code it is DumpOptions.StructSize func(path []uint16) int, returning 0 for anything that is not a struct vector, with Table.StructAt and Table.StructVectorLen underneath for reading the elements yourself.

Looking at one branch

On a payload of any size the dump is all or nothing. -depth cuts everywhere at once, so seeing one nested table in full means raising the depth and then reading past everything that came with it. -slot takes the same dotted path as -struct and starts there instead:

$ flatdump -slot 12.4 payload.bin
root @0x40 (168 bytes)
  slot 12  vector   2 tables
    [0]
      slot 4   scalar   u32=11 u16=11 u8=11
    [1]
      slot 4   scalar   u32=22 u16=22 u8=22

The slots on the way are kept, so the output says where in the buffer you are looking. Below the target the walk carries on as usual, -depth and -elems included: the flag picks where to start, not how much to print. Depth is still counted from the root, so reaching a long way in wants both.

A path that is not there says so:

$ flatdump -slot 10.99 payload.bin
root @0x40 (168 bytes)
  slot 10  table
  no slot 10.99 in this buffer

Note that slot 10 does exist, so the dump is not empty and judging by output length would call that a hit. An empty dump is indistinguishable from a buffer with nothing in it, which is the confusion this tool exists to remove, so it is answered directly. Under -json the same answer is "only_found": false.

For selecting rather than focusing, -json and jq were always enough and still are:

flatdump -json payload.bin | jq '.fields[] | select(.kind == "string")'

In code it is DumpOptions.Only []uint16, with flatread.OnPath exported for anyone writing their own walk who wants the same rule rather than a second copy of it.

The library

root, err := flatread.Root(buf)
if err != nil {
    return err
}

for _, slot := range root.Slots() {
    fmt.Println(slot, root.Guess(slot))
}

name := root.String(6)
ids  := root.Int32Vector(8)

if pricing, ok := root.Table(10); ok {
    total := pricing.Uint32(4)
}

for i := 0; i < root.VectorLen(12); i++ {
    if offer, ok := root.TableAt(12, i); ok {
        _ = offer.String(4)
    }
}

Scalars (Byte, Bool, Int8 through Int64, Uint16 through Uint64, Float32, Float64), strings, byte vectors, numeric and string vectors, nested tables and vectors of tables. Has distinguishes an absent field from one that's present and zero.

Entry points: Root for an ordinary buffer, RootSizePrefixed for one carrying a length, TableAtOffset when you already know where a table starts.

Vectors of structs

Structs are not tables. They're fixed-size records stored inline in the vector, with no vtable and no offsets, so TableAt cannot read them: it treats each element as an offset, adds a number that was never one, and returns a table pointing at whatever sits there. No error, no panic, just plausible nonsense.

StructAt reads them, given the element size from the schema:

n := root.StructVectorLen(6, 8)          // 8-byte records
for i := 0; i < n; i++ {
    rec := root.StructAt(6, i, 8)
    x := binary.LittleEndian.Uint32(rec)
    y := binary.LittleEndian.Uint32(rec[4:])
}

StructVectorLen also catches a wrong size, because the declared count multiplied by that size stops fitting the buffer. Plain VectorLen can't: it doesn't know how wide an element is.

Unions

A union becomes two fields in consecutive slots: the type tag at n, the value at n+2.

switch kind, value, ok := root.Union(6); {
case !ok:
    // absent, or the tag is UnionNone
case kind == 1:
    _ = value.String(4)
}

for i := 0; i < root.UnionVectorLen(10); i++ {
    kind, value, ok := root.UnionAt(10, i)
    ...
}

A tag of UnionNone in a union vector is a hole, not the end. Stopping there skips everything after it.

How a buffer is laid out

Worth having to hand while reading a dump. Everything is little-endian.

buf[0:4]                 uoffset to the root table

table[-soffset]          the table's vtable. soffset is a SIGNED int32
                         stored at the table's own position, so the
                         vtable normally sits before the table
vtable[0:2]              vtable size in bytes
vtable[2:4]              inline table size in bytes
vtable[4+2i : 6+2i]      byte offset of field i inside the table,
                         0 meaning absent

Field i lives at slot number 4+2i, so the first field is slot 4, the second slot 6, the third slot 8. Slot numbers are what you pass to every accessor. Without a schema they're the only stable handle you have.

Offsets to strings, vectors and nested tables are uoffsets: unsigned, and relative to the position of the offset itself.

Limitations

flatread recovers structure, not types. Guess works from a length prefix that fits, a run of printable bytes, a known magic number, or an offset that resolves to a readable vtable. That's genuinely all the information the buffer holds.

The clearest consequence, which has a test pinning it so it doesn't get "fixed" later: a vector of uint32{1, 2, 3} and the twelve bytes that encode it are the same twelve bytes, so Guess reports bytes. Nothing in a FlatBuffers buffer records a vector's element width.

The same applies to vectors of structs, which is why they need StructAt and an element size you supply.

So use Guess to find the slots you care about, confirm each one against real data, then read them with the typed accessors. Don't build a decoder on it.

flatread also doesn't write buffers. Use the official library for that. Once you know the schema you should be generating code from it anyway.

Safety

Every accessor is bounds-checked and returns a zero value rather than panicking. This package is by definition pointed at bytes nobody has validated, so malformed input is the ordinary case here, not an exceptional one.

That property is enforced rather than claimed:

go test -fuzz=FuzzReader

The fuzz target walks every accessor over arbitrary input, including offset cycles: a buffer whose field points back at its own table, which an encoder would never emit but a corrupt or hostile file can. CI runs a 60-second pass on every push.

The trade-off is that a zero return is ambiguous. It means either "the field holds zero" or "there is no such field". Use Has when the difference matters.

Roaring bitmaps

Byte vectors carrying a RoaringBitmap serial cookie are recognised and labelled, since Roaring turns up often in search and analytics payloads. Decoding one needs a dependency this package doesn't take, so there's a hook for it:

flatread.DumpWith(buf, flatread.DumpOptions{
    Annotate: func(b []byte) string {
        if !flatread.LooksRoaring(b) {
            return ""
        }
        bm := roaring.New()
        if _, err := bm.ReadFrom(bytes.NewReader(b)); err != nil {
            return "roaring: " + err.Error()
        }
        return fmt.Sprintf("%d bits set", bm.GetCardinality())
    },
})

License

MIT

Documentation

Overview

Package flatread reads FlatBuffers buffers for which you have no schema.

The normal FlatBuffers workflow compiles a .fbs schema into generated accessors, and field names live in that generated code. When you are handed a binary payload without its schema (reverse engineering a protocol, inspecting a capture, triaging a corrupt file) there is nothing to generate from, and the standard library is of no help.

This package reads a buffer positionally instead: fields are addressed by their vtable slot number rather than by name. That is enough to walk an unknown buffer, and enough to write a decoder once you have worked out which slot means what.

Buffer layout

Everything is little-endian. Reading the code below is much easier with this to hand:

buf[0:4]                 uoffset to the root table

table[-soffset]          the table's vtable (soffset is a SIGNED int32
                         stored at the table's own position)
vtable[0:2]              vtable size in bytes
vtable[2:4]              inline table size in bytes
vtable[4+2*i : 6+2*i]    byte offset of field i inside the table,
                         0 meaning the field is absent

So field i lives at slot number 4+2*i: the first field is slot 4, the second slot 6, and so on. Slot numbers, not indices, are what you pass here.

Offsets to strings, vectors and nested tables are uoffsets: unsigned, and relative to the position of the offset itself.

Safety

Every accessor is bounds-checked and returns a zero value rather than panicking. That is deliberate: this package exists to be pointed at bytes nobody has validated, so malformed input is the expected case and not an exceptional one. The fuzz test in this package asserts it.

The cost of that choice is that a zero value is ambiguous. It means either "the field holds zero" or "there is no such field". Use Table.Has when the difference matters.

Index

Examples

Constants

View Source
const UnionNone byte = 0

UnionNone is the type tag a union carries when it holds nothing. FlatBuffers reserves 0 for it in every union enum.

Variables

This section is empty.

Functions

func Dump

func Dump(buf []byte) string

Dump walks a buffer from its root and describes every populated field, guessing each one's type from its bytes.

This is the tool you reach for first when handed an unknown payload: it turns an opaque blob into a list of slot numbers with plausible types, which is what you need in order to start matching them against whatever documentation, generated code or client bundle you do have.

A malformed buffer produces a description of the error rather than a panic.

Example

Dump is the first thing to run against an unfamiliar payload: it turns an opaque blob into a list of slot numbers with plausible types.

Note slot 8, which really is a vector of uint32 {1, 2, 3} and is reported as three bytes. Nothing in a FlatBuffers buffer records a vector's element width, so that is the honest answer rather than a bug.

fmt.Print(Dump(sample()))
Output:
root @0x40 (168 bytes)
  slot 4   scalar   u32=4242 u16=4242 u8=146
  slot 6   string   "hello"
  slot 8   bytes    3
  slot 10  table
    slot 4   scalar   u32=7 u16=7 u8=7
  slot 12  vector   2 tables
    [0]
      slot 4   scalar   u32=11 u16=11 u8=11
    [1]
      slot 4   scalar   u32=22 u16=22 u8=22

func DumpWith

func DumpWith(buf []byte, opts DumpOptions) string

DumpWith is Dump with control over depth, breadth and annotation.

func FileIdentifier

func FileIdentifier(buf []byte) (string, bool)

FileIdentifier reads the optional four-byte identifier a schema can attach to its buffers, which sits directly after the root offset.

The bool reports whether those four bytes look like an identifier at all, meaning printable ASCII with no spaces. Nothing in the buffer records whether the field is present: a schema without a file_identifier puts padding or the start of a vtable in the same place. So a false here means "these bytes are not a plausible identifier", not "this schema has none".

When it is present it is usually the fastest way to work out what you have been handed, because it is chosen to be human-readable and often appears in the schema, the docs or the code that produced the buffer.

func IsSizePrefixed

func IsSizePrefixed(buf []byte) bool

IsSizePrefixed guesses whether buf carries a size prefix, by checking whether its first uint32 accounts for the rest of the buffer exactly.

Like Table.Guess this reads structure rather than a recorded fact, and it can be fooled: a plain buffer whose root offset happens to equal len(buf)-4 looks identical. It is a starting point for an unknown payload, not something to branch on in production once you know the format.

func LooksRoaring

func LooksRoaring(b []byte) bool

LooksRoaring reports whether b starts with a RoaringBitmap serial cookie: 12346 for a plain bitmap, or 12347 in the low 16 bits when run containers are present.

This only inspects the header. To actually decode one, hand the bytes to a Roaring library. This package deliberately has no dependencies.

func OnPath added in v0.6.0

func OnPath(only, path []uint16) bool

OnPath reports whether the slot at path is on the way to only, or inside it. An empty only matches everything.

This is the rule DumpOptions.Only follows: above the target, only the one branch leading to it; at or below it, everything, because Only picks a starting point and not a budget.

It is exported because a caller writing its own walk, as cmd/flatdump does for its JSON output, needs the same answer. A second copy of a rule like this is how two views of one buffer quietly start disagreeing.

Types

type Change added in v0.4.0

type Change struct {
	// Path is the slot path from the root, using dots for nested tables and
	// brackets for elements of a table vector: "4", "10.4", "12[0].4".
	Path string

	// A and B are the rendered values on each side. An empty string means the
	// slot is not present on that side, which [Change.String] shows as absent.
	A, B string
}

Change is one difference between two buffers.

func Diff added in v0.4.0

func Diff(a, b Table) []Change

Diff reports the slots that differ between two tables, walking nested tables and table vectors.

This is the workflow the tool exists for: capture the same request twice, or the same event before and after something changed, and ask which slots moved. It is far faster than reading two dumps side by side, and it does not need a schema, because a slot path is a stable handle whether or not you know what the slot means.

Values are compared as rendered text rather than as bytes. That is deliberate: two slots holding the same number are equal even if one buffer padded differently, and a change of kind (a string becoming a table) shows up as a change rather than as an error.

A nil result means the two tables agree everywhere Diff looked. Depth is capped, so it means "no difference within reach", not "the buffers are identical".

func (Change) String added in v0.4.0

func (c Change) String() string

type DumpOptions

type DumpOptions struct {
	// MaxDepth limits how far nested tables are followed. 0 means the default
	// of 5, which is deep enough to see the shape of most payloads without
	// printing a megabyte.
	MaxDepth int

	// MaxVectorElems limits how many elements of a table vector are expanded.
	// 0 means the default of 3. The remainder is summarised as "... N more".
	MaxVectorElems int

	// Annotate, when set, is called for every byte vector and its result is
	// appended to that line. This is the hook for anything that needs a
	// dependency: counting bits in a RoaringBitmap, sniffing a compression
	// header, decoding a nested buffer. Return "" to add nothing.
	Annotate func(b []byte) string

	// StructSize, when set, is asked how wide the elements of a struct vector
	// are. Return 0 for any slot that does not hold one.
	//
	// It has to be asked because nothing in the buffer answers it. A vector of
	// structs and a vector of bytes are stored identically, a count followed by
	// inline data with no vtable and no offsets, so [Table.Guess] reports
	// [KindBytes] for both. Without this a vector of three 8-byte structs dumps
	// as "bytes 3" and prints three bytes of the first one, which looks like a
	// tiny byte vector rather than 24 bytes of records.
	//
	// The path is the slot numbers from the root down to and including the slot
	// in question: root slot 6 is {6}, and slot 6 of the table in root slot 4
	// is {4, 6}. Indices into a table vector are not part of it, because every
	// element of a vector shares one schema, so a path names a position in the
	// schema rather than in the buffer.
	StructSize func(path []uint16) int

	// Only, when set, restricts the dump to one slot and everything under it.
	// It is a slot path from the root, spelled the same way as the one
	// StructSize is asked about.
	//
	// This is where to start, not how much to print. MaxDepth and
	// MaxVectorElems still apply below it, and MaxDepth is counted from the
	// root rather than from here, so reaching deep into a buffer wants both.
	//
	// A path that is not in the buffer says so rather than printing nothing.
	// An empty dump is indistinguishable from a buffer with nothing in it, and
	// telling those two apart is most of what this package is for.
	Only []uint16
}

DumpOptions controls DumpWith. The zero value is what Dump uses.

type Kind

type Kind int

Kind is a guess at what a field holds.

FlatBuffers does not record field types in the buffer. The schema carries them, and a schema is exactly what you do not have here. So Table.Guess leans entirely on structure: a length prefix that fits, bytes that are all printable, a known magic number, an offset that resolves to a readable vtable.

It is right often enough to be the fastest way into an unknown buffer, and it is wrong often enough that you should never build a decoder on it. Use it to find the slots you care about, confirm each one against real data, then read them with the typed accessors.

const (
	// KindScalar is the fallback: an inline number, or a field whose offset
	// resolves to nothing recognisable. Note that KindScalar is what an unknown
	// field degrades to, so it means "no better guess" rather than "definitely
	// a number".
	KindScalar Kind = iota

	// KindString is a length-prefixed run of printable ASCII.
	KindString

	// KindBytes is a length-prefixed run that is not printable and not
	// recognised. A vector of any fixed-width numeric type also lands here, and
	// so does a vector of structs: nothing in the buffer distinguishes a byte
	// vector from an int32 vector or from packed fixed-size records. Read those
	// with [Table.StructAt] once the schema tells you the element size.
	KindBytes

	// KindRoaring is a byte vector carrying a RoaringBitmap serial cookie.
	// Roaring is common in search and analytics payloads, compact enough to be
	// worth recognising, and its cookie is distinctive.
	KindRoaring

	// KindTable is an offset resolving to a plausible vtable.
	//
	// A nested table and a short byte vector look alike, because a table begins
	// with the offset back to its vtable and a vector begins with its length.
	// Both readings usually fit, so the structure of the target decides.
	// Measured against buffers built from a known schema, that recovers every
	// nested table and calls about 2% of genuine byte vectors tables, the
	// misses being short vectors whose bytes happen to read as a small vtable.
	KindTable

	// KindVectorOfTables is a vector whose elements all resolve to plausible
	// vtables.
	KindVectorOfTables
)

func (Kind) String

func (k Kind) String() string

String renders the Kind for display.

type Table

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

Table is a FlatBuffers table at a position inside a buffer. The zero Table is not usable; get one from Root.

A Table is a view, not a copy: it holds a reference to the underlying buffer and no accessor allocates unless it must (Table.String and the vector accessors do; the scalar ones do not).

Example

Once Dump has told you which slots exist, read them with the typed accessors. Slot numbers are the schema you did not get.

root, err := Root(sample())
if err != nil {
	panic(err)
}

fmt.Println("slots: ", root.Slots())
fmt.Println("string:", root.String(6))
fmt.Println("vector:", root.Int32Vector(8))

if nested, ok := root.Table(10); ok {
	fmt.Println("nested:", nested.Uint32(4))
}
for i := 0; i < root.VectorLen(12); i++ {
	if sub, ok := root.TableAt(12, i); ok {
		fmt.Printf("elem %d: %d\n", i, sub.Uint32(4))
	}
}
Output:
slots:  [4 6 8 10 12]
string: hello
vector: [1 2 3]
nested: 7
elem 0: 11
elem 1: 22

func Root

func Root(buf []byte) (Table, error)

Root returns the root table of a FlatBuffers buffer.

It fails only when the buffer is too small to hold a root offset, or when that offset points outside the buffer. It does NOT validate the rest of the buffer. That is what the accessors' bounds checks are for.

func RootSizePrefixed

func RootSizePrefixed(buf []byte) (Table, error)

RootSizePrefixed returns the root table of a size-prefixed buffer.

The size-prefixed layout puts a uint32 byte count in front of an otherwise ordinary buffer, so that a reader taking bytes off a stream knows where one message ends. gRPC uses it, and so does anything that stores several buffers back to back in one file.

Passing such a buffer to Root does not fail loudly. Root reads the size as the root offset, and what happens next depends on the number: usually an error, sometimes a table full of nonsense. If you do not know which layout you have, IsSizePrefixed guesses.

Trailing bytes past the declared size are ignored, so this can be called on a stream buffer that holds more than one message.

func TableAtOffset

func TableAtOffset(buf []byte, pos uint32) (Table, error)

TableAtOffset returns the table at an absolute byte position in buf.

Use it when you already know where a table starts, for instance when a size-prefixed buffer puts the root somewhere other than offset 0, or when you are re-entering a buffer at a position Table.Pos gave you earlier.

func (Table) Bool

func (t Table) Bool(slot uint16) bool

Bool reads a bool field. FlatBuffers stores these as a single byte.

func (Table) Buffer

func (t Table) Buffer() []byte

Buffer returns the underlying buffer. It is not copied.

func (Table) Byte

func (t Table) Byte(slot uint16) uint8

Byte reads a uint8 field.

func (Table) Bytes

func (t Table) Bytes(slot uint16) []byte

Bytes reads a vector of bytes, returned as a sub-slice of the buffer without copying. Do not retain it past the lifetime of the buffer, and do not write to it.

func (Table) BytesAt

func (t Table) BytesAt(slot uint16, i int) []byte

BytesAt reads element i of a vector of strings or byte vectors, without copying.

func (Table) Float32

func (t Table) Float32(slot uint16) float32

Float32 reads a float32 field.

func (Table) Float32Vector added in v0.3.0

func (t Table) Float32Vector(slot uint16) []float32

Float32Vector reads a vector of float32.

func (Table) Float64

func (t Table) Float64(slot uint16) float64

Float64 reads a float64 field.

func (Table) Float64Vector added in v0.3.0

func (t Table) Float64Vector(slot uint16) []float64

Float64Vector reads a vector of float64.

func (Table) Guess

func (t Table) Guess(slot uint16) Kind

Guess reports what a field most likely holds. See Kind for how much to trust it.

func (Table) Has

func (t Table) Has(slot uint16) bool

Has reports whether the field is present, as distinct from present and zero.

FlatBuffers omits fields equal to their default, so a writer that stored 0 may well have written nothing at all. Has cannot recover that distinction. It reports what is in the buffer, which is the most anyone can know.

func (Table) Int8

func (t Table) Int8(slot uint16) int8

Int8 reads an int8 field.

func (Table) Int16

func (t Table) Int16(slot uint16) int16

Int16 reads an int16 field.

func (Table) Int32

func (t Table) Int32(slot uint16) int32

Int32 reads an int32 field.

func (Table) Int32Vector

func (t Table) Int32Vector(slot uint16) []int32

Int32Vector reads a vector of int32.

func (Table) Int64

func (t Table) Int64(slot uint16) int64

Int64 reads an int64 field.

func (Table) Int64Vector

func (t Table) Int64Vector(slot uint16) []int64

Int64Vector reads a vector of int64.

func (Table) Pos

func (t Table) Pos() uint32

Pos is the table's absolute byte position in the buffer.

func (Table) Slots

func (t Table) Slots() []uint16

Slots lists the vtable slots this table actually populates, ascending.

This is the entry point for exploring an unknown buffer: it tells you which fields are present without knowing what any of them mean.

func (Table) String

func (t Table) String(slot uint16) string

String reads a string field. FlatBuffers strings are length-prefixed and NUL-terminated; the terminator is not included.

The bytes are not validated as UTF-8, because a schema-less buffer is exactly where a "string" often turns out to be something else.

func (Table) StringAt

func (t Table) StringAt(slot uint16, i int) string

StringAt reads element i of a vector of strings.

func (Table) StringVector

func (t Table) StringVector(slot uint16) []string

StringVector reads a vector of strings.

An element that is out of range comes back as "", the same as Table.StringAt would give, so the length of the result always matches the vector's declared length.

func (Table) StructAt added in v0.2.0

func (t Table) StructAt(slot uint16, i, size int) []byte

StructAt returns the bytes of element i in a vector of structs, without copying.

FlatBuffers structs are not tables. They are fixed-size records stored inline in the vector, one after another, with no vtable and no offsets. That makes them unreadable through Table.TableAt, which treats every element as an offset: on a struct vector it adds a number that was never an offset to the element position and hands back a Table pointing at whatever sits there.

The element size comes from the schema. Nothing in the buffer records it, which is also why Table.Guess cannot tell a struct vector from a byte vector and reports KindBytes for both.

Fields inside the returned bytes are at fixed offsets, again from the schema, and are read with encoding/binary directly.

func (Table) StructVectorLen added in v0.2.0

func (t Table) StructVectorLen(slot uint16, size int) int

StructVectorLen reports how many structs of the given size a vector holds, or 0 when the declared count does not fit the buffer.

This differs from Table.VectorLen, which trusts the count without knowing the element width. A truncated buffer shows up here and not there.

func (Table) Table

func (t Table) Table(slot uint16) (Table, bool)

Table reads a nested table field. The bool reports whether it was present and in range.

func (Table) TableAt

func (t Table) TableAt(slot uint16, i int) (Table, bool)

TableAt reads element i of a vector of tables.

func (Table) Uint16

func (t Table) Uint16(slot uint16) uint16

Uint16 reads a uint16 field.

func (Table) Uint32

func (t Table) Uint32(slot uint16) uint32

Uint32 reads a uint32 field.

func (Table) Uint32Vector

func (t Table) Uint32Vector(slot uint16) []uint32

Uint32Vector reads a vector of uint32.

func (Table) Uint64

func (t Table) Uint64(slot uint16) uint64

Uint64 reads a uint64 field.

func (Table) Union added in v0.3.0

func (t Table) Union(typeSlot uint16) (byte, Table, bool)

Union reads a union: its type tag and the table it points at. The bool is false when the tag is UnionNone or the value is missing.

Union members are usually tables, which is what this returns. A schema can also put a string or a struct in a union; for those, read typeSlot+2 directly with Table.String or Table.StructAt, since the value slot is an ordinary slot.

func (Table) UnionAt added in v0.3.0

func (t Table) UnionAt(typeSlot uint16, i int) (byte, Table, bool)

UnionAt reads element i of a vector of unions.

An element whose tag is UnionNone reports false. That is not an error and not the end of the vector: a union vector can hold empty slots between full ones, so keep walking to Table.UnionVectorLen.

func (Table) UnionHint added in v0.4.0

func (t Table) UnionHint(slot uint16) (UnionShape, bool)

UnionHint reports whether a slot looks like the tag half of a union.

Nothing in a buffer says "these two slots are a union". This checks the shape: a small tag next to a value that resolves, or a vector of tags the same length as a vector of values. Like Table.Guess it is a starting point for reading an unknown payload, not a fact to decode on, and the tools that use it say "maybe" rather than naming it.

Use Table.Union and Table.UnionAt once you know from the schema which slots really are unions.

func (Table) UnionType added in v0.3.0

func (t Table) UnionType(typeSlot uint16) byte

UnionType reads the type tag of a union without resolving its value.

A union in a schema becomes two fields in the table, in consecutive slots: the tag at typeSlot, saying which member is present, and the value at typeSlot+2. Without the schema the tag is just a number, but it is the number the schema's enum is keyed on, so it is what you match against once you know what the members are.

func (Table) UnionVectorLen added in v0.3.0

func (t Table) UnionVectorLen(typeSlot uint16) int

UnionVectorLen reports how many elements a vector of unions holds.

A union vector is two parallel vectors: the tags, as a vector of bytes at typeSlot, and the values at typeSlot+2. An encoder keeps them the same length. This reports the shorter of the two, so any index it accepts is in range for both, which matters on a truncated buffer where they are not.

func (Table) VectorLen

func (t Table) VectorLen(slot uint16) int

VectorLen reports the element count of a vector field, of any element type.

type UnionShape added in v0.4.0

type UnionShape struct {
	TagSlot   uint16
	ValueSlot uint16

	// Elements is 0 for a single union, or the element count for a vector of
	// unions.
	Elements int
}

UnionShape describes a pair of slots that look like the two halves of a union: the tag and the value it selects.

Directories

Path Synopsis
cmd
flatdump command
Command flatdump prints the structure of a FlatBuffers buffer you have no schema for.
Command flatdump prints the structure of a FlatBuffers buffer you have no schema for.

Jump to

Keyboard shortcuts

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