blob: f09bdd5ed8bba5d89ff42e285acaf6fb327e9f33 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package inspect
import (
"fmt"
"io"
)
const sizeBase = 16
const headerLen = 8
func writeUint16(w io.Writer, v uint16) error {
var b [2]byte
for i := range b {
b[i] = byte(v >> (i * 8))
}
_, err := w.Write(b[:])
return err
}
func writeUint24(w io.Writer, v uint32) error {
var b [3]byte
for i := range b {
b[i] = byte(v >> (i * 8))
}
_, err := w.Write(b[:])
return err
}
func writeUint32(w io.Writer, v uint32) error {
var b [4]byte
for i := range b {
b[i] = byte(v >> (i * 8))
}
_, err := w.Write(b[:])
return err
}
func writePadding(w io.Writer, width int) error {
_, err := w.Write(make([]byte, width))
return err
}
// Writer implements low-level streaming serialization in the inspect VMO
// format following the specification at
// https://fuchsia.dev/fuchsia-src/concepts/components/inspect/vmo_format.
type Writer struct {
writer io.Writer
nextIndex uint32
}
func NewWriter(writer io.Writer) (*Writer, error) {
w := Writer{
writer: writer,
}
return &w, w.writeHeaderBlock()
}
func (w *Writer) writeHeader(order uint8, typ uint8) error {
if _, err := w.writer.Write([]byte{order, typ}); err != nil {
return err
}
w.nextIndex += 1 << order
return nil
}
func (w *Writer) writeHeaderBlock() error {
// Header.
if err := w.writeHeader(0, 2); err != nil {
return err
}
// Version.
if err := writeUint16(w.writer, 1); err != nil {
return err
}
if _, err := io.WriteString(w.writer, "INSP"); err != nil {
return err
}
// Generation count.
if err := writePadding(w.writer, 8); err != nil {
return err
}
return nil
}
func (w *Writer) writeNameBlock(name string) (uint32, error) {
const maxLength = 1<<13 - 1
if len(name) > maxLength {
return 0, fmt.Errorf("name too long: %d/%d bytes", len(name), maxLength)
}
index := w.nextIndex
var order uint8
for len(name) > sizeBase<<order-headerLen {
order++
}
// Header.
if err := w.writeHeader(order, 9); err != nil {
return 0, err
}
// Length. NB: length is 12 bits, but is followed by 36 reserved bits.
if err := writeUint16(w.writer, uint16(len(name))); err != nil {
return 0, err
}
// Reserved.
if err := writePadding(w.writer, 4); err != nil {
return 0, err
}
// Payload.
n, err := io.WriteString(w.writer, name)
if err != nil {
return 0, err
}
// Padding.
if err := writePadding(w.writer, sizeBase<<order-headerLen-n); err != nil {
return 0, err
}
return index, nil
}
func (w *Writer) WriteNodeValueBlock(parentIndex uint32, name string) (uint32, error) {
nodeValueNameIndex, err := w.writeNameBlock(name)
if err != nil {
return 0, err
}
nodeValueIndex := w.nextIndex
// NodeValue.
if err := w.writeHeader(0, 3); err != nil {
return 0, err
}
// Parent index.
if err := writeUint24(w.writer, parentIndex); err != nil {
return 0, err
}
// Name index.
if err := writeUint24(w.writer, nodeValueNameIndex); err != nil {
return 0, err
}
// Reference count.
if err := writePadding(w.writer, 8); err != nil {
return 0, err
}
return nodeValueIndex, nil
}
func (w *Writer) WriteBinary(parentIndex uint32, name string, size uint32, r io.Reader) error {
bufferValueNameIndex, err := w.writeNameBlock(name)
if err != nil {
return err
}
// BufferValue.
if err := w.writeHeader(0, 7); err != nil {
return err
}
// Parent index.
if err := writeUint24(w.writer, parentIndex); err != nil {
return err
}
// Name index.
if err := writeUint24(w.writer, bufferValueNameIndex); err != nil {
return err
}
// Total length.
if err := writeUint32(w.writer, size); err != nil {
return err
}
// Extent index.
if err := writeUint24(w.writer, w.nextIndex); err != nil {
return err
}
// Display format = binary.
if _, err := w.writer.Write([]byte{1 << 4}); err != nil {
return err
}
// Extent.
for {
var order uint8
for order < 7 && size > sizeBase<<order-headerLen {
order++
}
if err := w.writeHeader(order, 8); err != nil {
return err
}
// Next extent index.
if err := writeUint24(w.writer, w.nextIndex); err != nil {
return err
}
// Reserved.
if err := writePadding(w.writer, 3); err != nil {
return err
}
n, err := io.CopyN(w.writer, r, sizeBase<<order-headerLen)
if err == io.EOF {
if err := writePadding(w.writer, sizeBase<<order-headerLen-int(n)); err != nil {
return err
}
break
} else if err != nil {
return err
}
}
return nil
}