[inspect][dart] Low-level API

Creates the initial Dart Inspect package.

Includes a low-level architecture for VMO access, with an API supporting
creating, modifying, and deleting Nodes, Properties, and Values.
See vmo_writer.dart.

Nothing is implemented yet; placeholder code is trivially wrong.

Test: builds
CF-601 #progress

Change-Id: I6f01377430bfe6340367c39ca37087e365d3863b
diff --git a/packages/tests/dart_unittests b/packages/tests/dart_unittests
index d2c7101..4a99013 100644
--- a/packages/tests/dart_unittests
+++ b/packages/tests/dart_unittests
@@ -12,6 +12,7 @@
       "//topaz/lib/setui/settings/common:lib_setui_settings_common_test",
       "//topaz/lib/setui/settings/service:lib_setui_service_test",
       "//topaz/lib/setui/settings/testing:lib_setui_settings_testing_test",
+      "//topaz/public/dart/fuchsia_inspect:fuchsia_inspect_package_unittests",
       "//topaz/public/dart/fuchsia_logger:fuchsia_logger_package_unittests",
       "//topaz/public/dart/fuchsia_modular:fuchsia_modular_package_unittests",
       "//topaz/public/dart/fuchsia_services:fuchsia_services_package_unittests",
diff --git a/public/dart/fuchsia_inspect/BUILD.gn b/public/dart/fuchsia_inspect/BUILD.gn
new file mode 100644
index 0000000..eb7df06
--- /dev/null
+++ b/public/dart/fuchsia_inspect/BUILD.gn
@@ -0,0 +1,43 @@
+# Copyright 2019 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.
+
+import("//build/dart/dart_library.gni")
+import("//topaz/runtime/dart/dart_test.gni")
+
+dart_library("fuchsia_inspect") {
+  package_name = "fuchsia_inspect"
+
+  sdk_category = "partner"
+
+  source_dir = "lib"
+
+  sources = [
+    "inspect.dart",
+    "src/vmo_writer.dart",
+    "src/vmo_heap.dart",
+    "src/inspect.dart",
+  ]
+
+  deps = [
+    "//third_party/dart-pkg/pub/meta",
+    "//topaz/public/dart/fuchsia_services",
+    "//topaz/public/dart/zircon",
+  ]
+}
+
+# Runs these tests using:
+#   fx run-host-tests fuchsia_inspect_package_unittests
+
+dart_test("fuchsia_inspect_package_unittests") {
+  sources = [
+    "internal/vmo_state_test.dart",
+  ]
+
+  deps = [
+    ":fuchsia_inspect",
+    "//third_party/dart-pkg/pub/mockito",
+    "//third_party/dart-pkg/pub/test",
+  ]
+}
+
diff --git a/public/dart/fuchsia_inspect/analysis_options.yaml b/public/dart/fuchsia_inspect/analysis_options.yaml
new file mode 100644
index 0000000..bebf512
--- /dev/null
+++ b/public/dart/fuchsia_inspect/analysis_options.yaml
@@ -0,0 +1,5 @@
+# Copyright 2019 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.
+
+include: ../../analysis_options.yaml
diff --git a/public/dart/fuchsia_inspect/lib/inspect.dart b/public/dart/fuchsia_inspect/lib/inspect.dart
new file mode 100644
index 0000000..fe3ebfd
--- /dev/null
+++ b/public/dart/fuchsia_inspect/lib/inspect.dart
@@ -0,0 +1,6 @@
+// Copyright 2019 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.
+
+/// The Inspect API for Dart.
+export 'src/inspect.dart';
diff --git a/public/dart/fuchsia_inspect/lib/src/inspect.dart b/public/dart/fuchsia_inspect/lib/src/inspect.dart
new file mode 100644
index 0000000..0e21806
--- /dev/null
+++ b/public/dart/fuchsia_inspect/lib/src/inspect.dart
@@ -0,0 +1,18 @@
+// Copyright 2019 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.
+
+import 'vmo_writer.dart';
+
+const int _defaultVmoSizeBytes = 256 * 1024;
+
+/// Inspect exposes a structured tree of internal component state in a VMO.
+class Inspect {
+  final VmoWriter _vmo;
+
+  /// Initialize the VMO with given or default size.
+  Inspect([vmoSize = _defaultVmoSizeBytes]) : _vmo = VmoWriter(vmoSize);
+
+  /// Placeholder for the upper-level API.
+  bool get valid => _vmo != null;
+}
diff --git a/public/dart/fuchsia_inspect/lib/src/vmo_format.dart b/public/dart/fuchsia_inspect/lib/src/vmo_format.dart
new file mode 100644
index 0000000..3dd4870
--- /dev/null
+++ b/public/dart/fuchsia_inspect/lib/src/vmo_format.dart
@@ -0,0 +1,70 @@
+// Copyright 2019 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.
+
+import 'dart:typed_data';
+
+import 'vmo_holder.dart';
+
+/// Types of VMO blocks.
+enum BlockType {
+  /// One block to rule them all. Index 0.
+  header,
+
+  /// Ready to be used.
+  free,
+
+  /// In transition toward being used.
+  reserved,
+
+  /// An entry in the Inspect tree, which may hold child Values: Nodes, Metrics, or Properties.
+  node,
+
+  /// An int Metric.
+  intValue,
+
+  /// A uint Metric.
+  uintValue,
+
+  /// A double Metric.
+  doubleValue,
+
+  /// A property that's been deleted but still has live children.
+  tombstone,
+
+  /// The header of a string (or byte-vector) Property.
+  propertyValue,
+
+  /// The contents of a string Property (in a singly linked list, if necessary).
+  extent,
+
+  /// The name of a Value (Property, Metric, or Node) (must be contained in this one block).
+  name
+}
+
+/// Mirrors a single block in the VMO.
+///
+/// Can be read from VMO and/or modified by code, then written to VMO if desired.
+class Block {
+  /// Order: size = 2^(order+4); order is 1 in this slab allocator.
+  int order = 1;
+
+  /// The VMO-format-defined type of this block.
+  BlockType type;
+
+  /// Index of the block within the VMO.
+  final int index;
+
+  /// Reads the block at this index from the VMO.
+  Block.read(VmoHolder vmo, this.index) {
+    ByteData data = vmo.read(index * 16, 32);
+    int header = data.getUint8(0);
+    // TODO(cphoenix): Replace this with Bitfield64 and less-magic bit locations.
+    type = BlockType.values[header & 0x0F];
+    order = header >> 4;
+    // TODO(cphoenix): Read the rest of the block, depending on type.
+  }
+
+  /// Size of the [Block] in bytes.
+  int get size => 1 << (order + 4);
+}
diff --git a/public/dart/fuchsia_inspect/lib/src/vmo_heap.dart b/public/dart/fuchsia_inspect/lib/src/vmo_heap.dart
new file mode 100644
index 0000000..455f0c7
--- /dev/null
+++ b/public/dart/fuchsia_inspect/lib/src/vmo_heap.dart
@@ -0,0 +1,50 @@
+// Copyright 2019 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.
+
+import 'vmo_holder.dart';
+
+const int _blockSize = 32;
+
+/// The base class for which log writers will inherit from.
+///
+/// (The current implementation is a barely-MVP heap: a 32-byte-block slab allocator.)
+class VmoHeap extends VmoHolder {
+  /// Size in bytes of the touched / visited subset of the VMO incorporated in the data structure.
+  int _currentSize;
+
+  /// Max size of the VMO in bytes.
+  final int _maxSize;
+
+  /// Offset of the first block on the freelist.
+  int _freelistHead = 0;
+
+  /// Construct with (initially touched size, maximum available size) of VMO.
+  VmoHeap(this._currentSize, this._maxSize) : super(_maxSize) {
+    _addFreelistBlocks(0, _currentSize);
+  }
+
+  void _addFreelistBlocks(int existingSize, int newSize) {
+    // Placeholder code, not tested, certainly wrong.
+    _freelistHead = existingSize;
+    int i;
+    for (i = existingSize; i < newSize - _blockSize; i += _blockSize) {
+      writeInt64(i, i + _blockSize);
+    }
+    writeInt64(i, _freelistHead);
+    _freelistHead = existingSize;
+  }
+
+  /// Maps more of the VMO.
+  void growVmo(int desiredSize) {
+    if (_currentSize == _maxSize) {
+      return; // Fail silently.
+    }
+    int newSize = desiredSize;
+    if (newSize > _maxSize) {
+      newSize = _maxSize;
+    }
+    _addFreelistBlocks(_currentSize, newSize);
+    _currentSize = newSize;
+  }
+}
diff --git a/public/dart/fuchsia_inspect/lib/src/vmo_holder.dart b/public/dart/fuchsia_inspect/lib/src/vmo_holder.dart
new file mode 100644
index 0000000..13a2d36
--- /dev/null
+++ b/public/dart/fuchsia_inspect/lib/src/vmo_holder.dart
@@ -0,0 +1,52 @@
+// Copyright 2019 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.
+
+import 'dart:typed_data';
+import 'package:zircon/zircon.dart';
+
+/// Holder for a VMO with read/write capability.
+class VmoHolder {
+  int _size;
+  Vmo _vmo;
+
+  /// Creates and holds a VMO of desired size.
+  VmoHolder(this._size) {
+    HandleResult result = System.vmoCreate(_size);
+    if (result.status != ZX.OK) {
+      throw new ZxStatusException(
+          result.status, getStringForStatus(result.status));
+    }
+    _vmo = Vmo(result.handle);
+  }
+
+  /// Writes data to VMO at offset (not index).
+  void write(int offset, ByteData data) {
+    int status = _vmo.write(data, offset);
+    if (status != ZX.OK) {
+      throw new ZxStatusException(status, getStringForStatus(status));
+    }
+  }
+
+  /// Reads data from VMO at offset (not index).
+  ByteData read(int offset, int size) {
+    ReadResult result = _vmo.read(size, offset);
+    if (result.status != ZX.OK) {
+      throw new ZxStatusException(
+          result.status, getStringForStatus(result.status));
+    }
+    return result.bytes;
+  }
+
+  /// Writes int64 to VMO.
+  void writeInt64(int offset, int value) {
+    var data = ByteData(8)..setInt64(0, value, Endian.little);
+    write(offset, data);
+  }
+
+  /// Reads int64 from VMO.
+  int readInt64(int offset) {
+    ByteData data = read(8, offset);
+    return data.getInt64(0, Endian.little);
+  }
+}
diff --git a/public/dart/fuchsia_inspect/lib/src/vmo_writer.dart b/public/dart/fuchsia_inspect/lib/src/vmo_writer.dart
new file mode 100644
index 0000000..85cf57b
--- /dev/null
+++ b/public/dart/fuchsia_inspect/lib/src/vmo_writer.dart
@@ -0,0 +1,96 @@
+// Copyright 2019 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.
+
+import 'dart:typed_data';
+
+import 'vmo_heap.dart';
+
+/// Opaque information referring to a Value (Property, Metric, Node) stored in the VMO.
+///
+/// The user of this API should not care about what's in InspectHandle - just hold it
+/// and pass it back to VmoWriter for further operations on the Value referred to by the handle.
+class InspectHandle {
+  /// First opaque number.
+  int opaque1;
+
+  /// Second opaque number.
+  int opaque2;
+
+  /// Constructor
+  InspectHandle(this.opaque1, this.opaque2);
+}
+
+/// An Inspect-format VMO with accessors.
+///
+/// This holds a VMO, writes Nodes, Metrics, and Properties to
+/// the VMO, modifies them, and deletes them.
+class VmoWriter {
+  final VmoHeap _vmo;
+
+  /// Constructor.
+  ///
+  /// maxSize should be >= 32 bytes and will be rounded up to a multiple of 4K.
+  VmoWriter(int maxSizeBytes) : _vmo = VmoHeap(maxSizeBytes, maxSizeBytes);
+
+  // All the implementations here are trivially wrong placeholders.
+  // For now, just look at the method signature.
+
+  /// Gets the top Node of the Inspect tree.
+  InspectHandle get rootNode => InspectHandle(1, 2);
+
+  /// Creates a new Node inside the tree.
+  InspectHandle createNode(InspectHandle parent, String name) {
+    return parent;
+  }
+
+  /// Frees the Node.
+  void freeNode(InspectHandle node) {
+    _vmo.writeInt64(node.opaque1, 0);
+  }
+
+  /// Adds a named Property to an node.
+  InspectHandle createProperty(InspectHandle parentNode, ByteData name) {
+    _vmo.write(parentNode.opaque1, name);
+    return parentNode;
+  }
+
+  /// Sets a Property's value.
+  void setProperty(InspectHandle property, ByteData value) {
+    _vmo.write(property.opaque1, value);
+  }
+
+  /// Deletes a Property.
+  void freeProperty(InspectHandle property) {
+    _vmo.writeInt64(property.opaque1, 0);
+  }
+
+  // TODO(cphoenix): Convert to generic for Int and Double (not Uint).
+
+  /// Creates and assigns value.
+  InspectHandle createIntMetric(
+      InspectHandle parentNode, String name, int value) {
+    _vmo.writeInt64(parentNode.opaque1, value);
+    return parentNode;
+  }
+
+  /// Sets the metric's value.
+  void setIntMetric(InspectHandle metric, int value) {
+    _vmo.writeInt64(metric.opaque1, value);
+  }
+
+  /// Adds to existing value.
+  void addIntMetric(InspectHandle metric, int value) {
+    _vmo.writeInt64(metric.opaque1, value);
+  }
+
+  /// Subtracts from existing value.
+  void subtractIntMetric(InspectHandle metric, int value) {
+    _vmo.writeInt64(metric.opaque1, value);
+  }
+
+  /// Deletes the Metric.
+  void freeIntMetric(InspectHandle metric) {
+    _vmo.writeInt64(metric.opaque1, 0);
+  }
+}
diff --git a/public/dart/fuchsia_inspect/pubspec.yaml b/public/dart/fuchsia_inspect/pubspec.yaml
new file mode 100644
index 0000000..2d6dba0
--- /dev/null
+++ b/public/dart/fuchsia_inspect/pubspec.yaml
@@ -0,0 +1,7 @@
+# Copyright 2019 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.
+
+name: fuchsia_inspect
+environment:
+  sdk: '>=2.0.0 <3.0.0'
diff --git a/public/dart/fuchsia_inspect/test/internal/vmo_state_test.dart b/public/dart/fuchsia_inspect/test/internal/vmo_state_test.dart
new file mode 100644
index 0000000..0c345d1
--- /dev/null
+++ b/public/dart/fuchsia_inspect/test/internal/vmo_state_test.dart
@@ -0,0 +1,22 @@
+// Copyright 2019 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.
+
+// ignore_for_file: implementation_imports, unused_import
+
+// Since we don't have tests yet, import everything to make sure it at least compiles.
+import 'package:test/test.dart';
+import 'package:fuchsia_inspect/inspect.dart';
+import 'package:fuchsia_inspect/src/inspect.dart';
+import 'package:fuchsia_inspect/src/vmo_format.dart';
+import 'package:fuchsia_inspect/src/vmo_heap.dart';
+import 'package:fuchsia_inspect/src/vmo_holder.dart';
+import 'package:fuchsia_inspect/src/vmo_writer.dart';
+
+void main() {
+  group('placeholder for tests', () {
+    test('trivial', () {
+      expect(41, equals(41));
+    });
+  });
+}